---
# System prepended metadata

title: JavaScript 關於 this 在 addEventListener 下的歸屬

---

## 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 ![this 使用講解](https://hackmd.io/_uploads/BJlfSxB-yR.png)

- MDN 監聽器連結以及講解 https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener ![監聽器跟this的關係](https://hackmd.io/_uploads/Hy0W1HZyA.png)

- MDN 箭頭函數連結以及講解 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions
![箭頭函數作用域跟this的關係](https://hackmd.io/_uploads/rJnUAE-kR.png)
- MDN setTimeout連結以及講解 https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout ![setTimeout裡的this作用](https://hackmd.io/_uploads/rJe6JBZyA.png)

```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

```