## this 監聽器裡所指向的地方,要使用 this 就不要寫箭頭函數,尤其在監聽器裡
1. 監聽器裡的回呼函數使用一般函數編寫,在這個函數的 this 的作用域會指向監聽器的元素
1. 回呼函數使用一般函數編寫狀況下,this 指向監聽器的元素,此時在裡面宣告的箭頭函數會的作用域會指向, btn , 不過如果被包在 一般函數 或 setTimeout 底下的 this 則指向 全域或 window
2. 如果箭頭數 被包在一般函數 或 setTimeout 裡 this 的指向是 全域或 window
3. 若是箭頭函數裡又有箭頭函數, this 依然指向 btn 元素,不管你包幾層
3. 監聽器裡的回呼函數使用箭頭函數編寫,會造成 this 指向全域,因為箭頭數沒有 this argument super.
4. setTimeout 第一個參數函數要用 bind 來綁定 this ,不然默認全域或 window
### 監聽器的 callback fn 用一般函數編寫,此時回呼函數作用域的 this 會指向 btn 元素 .
- MDN this連結 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this 
- MDN 監聽器連結以及講解 https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener 
- MDN 箭頭函數連結以及講解 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions

- MDN setTimeout連結以及講解 https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout 
```js=
// HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="app.js" defer></script>
</head>
<body>
<button class="btn">RED</button>
<button class="btn">GREEN</button>
<button class="btn">BLUE</button>
</body>
</html>
```
***
```js=
// JS
const btns = document.querySelectorAll(".btn")
btns.forEach((btn) =>{
btn.addEventListener("click", function(e) {
//閉包
//使用 this.className 找出 this 所在元素的名字
//使用 e 參數裡的屬性 currentTarget 驗證 this 的方向
console.log(e)
console.log(this.className); // 输出 my_element 的 className
console.log(e.currentTarget === this); // 输出 `true`
console.log("btn 元素回呼函數 呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
console.log("console.log(this), this 指向 btn 這個元素", this)
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerA() 一般函數呼叫
function handlerA() {
console.log(this)
return this
}
console.log("一般函數呼叫 handlerA() , this 指向全域物件", handlerA())
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerB() 宣告函數呼叫
const handlerB = function() {
console.log(this);
return this
}
console.log("宣告函數叫 handlerB() , this 指向全域物件", handlerB())
console.log("~~~~~~~~~~~~~~~~~~~~")
// 這裡很吊跪
// handlerC() 箭頭函數呼叫 (笨蛋,箭頭函數沒 this,不用想了,結果我錯了跟我想的不一樣... 它指向了 btn 元素)
const handlerC = () => {
console.log(this)
return this
}
console.log("箭頭函數呼叫 handlerC() , this 指向全域物件", handlerC())
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("call 綁定呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
// call
console.log("這裡開始是 call 的 驗證 this 區塊")
// handlerA() 一般函數 加上 call 呼叫
// handlerA.call(this)
console.log("一般函數加上 call 呼叫 handlerA() , this 指向 btn 元素", handlerA.call(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerB() 宣告函數 加上 call 呼叫
// handlerB.call(this)
console.log("宣告函數加上 call 呼叫 handlerB() , this 指向 btn 元素", handlerB.call(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerC() 宣告函數 加上 call 呼叫
// handlerC.call(this)
console.log("宣告函數加上 call 呼叫 handlerC() , this 指向 btn 元素", handlerC.call(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("apply 綁定呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
// apply
console.log("這裡開始是 apply 的 驗證 this 區塊")
// handlerA() 一般函數 加上 apply 呼叫
// handlerA.apply(this)
console.log("一般函數加上 apply 呼叫 handlerA() , this 指向 btn 元素", handlerA.apply(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerB() 宣告函數 加上 apply 呼叫
// handlerB.apply(this)
console.log("宣告函數加上 apply 呼叫 handlerB() , this 指向 btn 元素", handlerB.apply(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerC() 箭頭函數 加上 apply 呼叫
// handlerC.apply(this)
console.log("宣告函數加上 apply 呼叫 handlerC() , this 指向 btn 元素", handlerC.apply(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("bind 綁定呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
// bind 使用 bind 方法會得到一顆函數,等待下一次被呼叫執行
console.log("這裡開始是 bind 的 驗證 this 區塊")
// 宣告 newHanderA = handlerA() 函數綁定 this ,目前 this 指定 btn 這個元素
const bindHanderA = handlerA.bind(this)
console.log("宣告函數呼叫 bindHanderA(), this 指向 btn 元素", bindHanderA())
console.log("~~~~~~~~~~~~~~~~~~~~")
// 宣告 newHanderB = handlerB() 函數綁定 this ,目前 this 指定 btn 這個元素
const bindHanderB = handlerB.bind(this)
console.log("宣告函數呼叫 bindHanderB(), this 指向 btn 元素", bindHanderB())
console.log("~~~~~~~~~~~~~~~~~~~~")
// 宣告 newHanderC = handlerC() 函數綁定 this ,目前 this 指定 btn 這個元素
const bindHanderC = handlerC.bind(this)
console.log("宣告函數呼叫 bindHanderC(), this 指向 btn 元素", bindHanderC())
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("setTimout 測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
console.log("setTimeout call 測試, 請不要跟著以下寫 CODE setTimeout 第一個參數是是放函數,不然會送你一條錯誤訊息")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(handlerA.call(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 btn 元素 , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerB.call(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 btn 元素 , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerC.call(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 btn 元素 , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
console.log("setTimeout apply 測試, 請不要跟著以下寫 CODE setTimeout 第一個參數是是放函數,不然會送你一條錯誤訊息")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(handlerA.apply(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 btn 元素 , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerB.apply(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 btn 元素 , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerC.apply(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 btn 元素 , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
console.log("setTimeout bind 測試")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(handlerA.bind(this), 1000) // 使用 bind 會印出 btn 元素
setTimeout(handlerB.bind(this), 1000) // 使用 bind 會印出 btn 元素
setTimeout(handlerC.bind(this), 1000) // 使用 bind 會印出 btn 元素
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
console.log("setTimeout bind之二 測試")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(bindHanderA, 1000) // 使用 bind 會印出 btn 元素
setTimeout(bindHanderB, 1000) // 使用 bind 會印出 btn 元素
setTimeout(bindHanderC, 1000) // 使用 bind 會印出 btn 元素
})
})
```
### 監聽器的 callback fn 用箭頭函數編寫,此時回呼函數的作用域 this 會指向 全域(瀏覽器是 window) .
```js=
// JS
const btns1 = document.querySelectorAll(".btn")
btns1.forEach((btn) =>{
btn.addEventListener("click", (e) => {
//閉包
console.log("e", e)
console.log(this.className); // 输出 my_element 的 className undefined
console.log(e.currentTarget === this); // 输出 `false`
console.log("btn 元素回呼函數 呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
console.log("console.log(this), this 指向 全域物件", this)
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerA() 一般函數呼叫
function handlerA() {
console.log(this)
return this
}
console.log("一般函數呼叫 handlerA() , this 指向全域物件", handlerA())
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerB() 宣告函數呼叫
const handlerB = function() {
console.log(this);
return this
}
console.log("宣告函數叫 handlerB() , this 指向全域物件", handlerB())
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerC() 箭頭函數呼叫
const handlerC = () => {
console.log(this)
return this
}
console.log("箭頭函數呼叫 handlerC() , this 指向全域物件", handlerC())
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("call 綁定呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
// call
console.log("這裡開始是 call 的 驗證 this 區塊")
// handlerA() 一般函數 加上 call 呼叫
// handlerA.call(this)
console.log("一般函數加上 call 呼叫 handlerA() , this 指向全域物件 ", handlerA.call(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerB() 宣告函數 加上 call 呼叫
// handlerB.call(this)
console.log("宣告函數加上 call 呼叫 handlerB() , this 指向全域物件", handlerB.call(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerC() 宣告函數 加上 call 呼叫
// handlerC.call(this)
console.log("宣告函數加上 call 呼叫 handlerC() , this 指向全域物件", handlerC.call(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("apply 綁定呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
// apply
console.log("這裡開始是 apply 的 驗證 this 區塊")
// handlerA() 一般函數 加上 apply 呼叫
// handlerA.apply(this)
console.log("一般函數加上 apply 呼叫 handlerA() , this 指向全域物件", handlerA.apply(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerB() 宣告函數 加上 apply 呼叫
// handlerB.apply(this)
console.log("宣告函數加上 apply 呼叫 handlerB() , this 指向全域物件", handlerB.apply(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
// handlerC() 箭頭函數 加上 apply 呼叫
// handlerC.apply(this)
console.log("宣告函數加上 apply 呼叫 handlerC() , this 指向全域物件", handlerC.apply(this))
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("bind 綁定呼叫測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
// bind 使用 bind 方法會得到一顆函數,等待下一次被呼叫執行
console.log("這裡開始是 bind 的 驗證 this 區塊")
// 宣告 newHanderA = handlerA() 函數綁定 this ,目前 this 指定 指向全域物件
const bindHanderA = handlerA.bind(this)
console.log("宣告函數呼叫 bindHanderA(), this 指向全域物件", bindHanderA())
console.log("~~~~~~~~~~~~~~~~~~~~")
// 宣告 newHanderB = handlerB() 函數綁定 this ,目前 this 指定 指向全域物件
const bindHanderB = handlerB.bind(this)
console.log("宣告函數呼叫 bindHanderB(), this 指向全域物件", bindHanderB())
console.log("~~~~~~~~~~~~~~~~~~~~")
// 宣告 newHanderC = handlerC() 函數綁定 this ,目前 this 指定 指向全域物件
const bindHanderC = handlerC.bind(this)
console.log("宣告函數呼叫 bindHanderC(), this 指向全域物件", bindHanderC())
console.log("~~~~~~~~~~~~~~~~~~~~")
console.log("setTimout 測試 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
console.log("setTimeout call 測試, 請不要跟著以下寫 CODE setTimeout 第一個參數是是放函數,不然會送你一條錯誤訊息")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(handlerA.call(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 window , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerB.call(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 window , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerC.call(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出window, 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
console.log("setTimeout apply 測試, 請不要跟著以下寫 CODE setTimeout 第一個參數是是放函數,不然會送你一條錯誤訊息")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(handlerA.apply(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 window , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerB.apply(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 window , 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
setTimeout(handlerC.apply(this), 1000) // setTimeout 第一個參數只能放函數,這裡純測試,使用 call 會印出 window, 順送你一行 SyntaxError: Unexpected identifier 'HTMLButtonElement'
console.log("setTimeout bind 測試")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(handlerA.bind(this), 1000) // 使用 bind 會印出 window
setTimeout(handlerB.bind(this), 1000) // 使用 bind 會印出 window
setTimeout(handlerC.bind(this), 1000) // 使用 bind 會印出 window
console.log("setTimeout bind之二 測試")
// call 測試帶進的 this 是全域還是當下指定的物件
setTimeout(bindHanderA, 1000) // 使用 bind 會印出 window
setTimeout(bindHanderB, 1000) // 使用 bind 會印出 window
setTimeout(bindHanderC, 1000) // 使用 bind 會印出 window
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
})
})
```
### 監聽器下箭頭函數裡有一般函數跟箭頭函數的結果
```js=
// JS
```