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