人稱 JS 的頭號公敵。
this 最原始的用途
回歸到原點,其實 this
本身就是為了物件導向而存在的東西,用來指向你建立出來的 instance。
例如說:
1 | class Dog { |
首先,我透過 Dog
建立了一個 instance:dog1
記住,只要是出現在 dog1
裡面的 this 都一律代表它自己,不會有任何例外情形發生。在物件導向下的 this 就是這麼單純,沒有一大堆莫名其妙的情況發生。
所以我一開始給 dog1
的 name 是 PeaNu
,那麼它裡面的 this.name
就只會是 PeaNu。
之後如果又建立了另一個 dog2
,把它的 name 設為 PPB
,那它的 this.name
也只會是 PPB,跟 dog1
一點關係也沒有。
總而言之,this
的初衷就是用來代表「這個 instance」的意思,非常非常非常單純。
在非物件導向的環境下,this 的值沒有意義
如果你硬要在不是物件導向的地方用 this
,就會出現一些奇怪的結果:
1 | function test() { |
在這種情況下,this 就會根據不同的環境而有不同的值。
undefined
,在使用嚴格模式下的預設值window
,在瀏覽器下的預設值global
在 Node.js 下的預設值
不管最後的值是什麼,這種 this
都沒什麼意義,所以才會有這個標題。
想知道 this 值,得看是怎麼呼叫的,不是宣告
舉個例子:
1 | 'use strict' |
同樣都是呼叫 test
,但第一個結果是 obj,第二個結果是 undefined,為什麼?因為呼叫的方式不同。
第一個 test 是透過 obj
來呼叫的,而第二個是直接執行 func
,這兩種的呼叫方式是不一樣的。所以儘管 test
是宣告在 obj 這個物件裡,但只要用不同的方式來呼叫它,this 的值就會不同。
再次強調:
- 重點是怎麼呼叫,而不是宣告
- 重點是怎麼呼叫,而不是宣告
- 重點是怎麼呼叫,而不是宣告
所以再出一題來考考你,下面的 this 值會是什麼?
1 | 'use strict' |
想完後就貼到 console,看跟自己想的一不一樣。
透過 call、apply 和 bind 來改變 this 值
既然 this
值會變來變去的,那有沒有辦法控制它?
有,就用 call
、apply
和 bind
來控制,舉個範例:
1 | 'use strict' |
call
和 apply
是另一種執行 function 的方式,跟 ()
的差別在於它們可以傳入一個參數,這個參數就是用來指定 this 的值,你傳什麼進去就會出來什麼。
至於 bind
比較特別一點,一樣先看例子:
1 | 'use strict' |
首先 bind
的作用不是用來呼叫 function,而是把綁定 this 後的 function 給回傳,以上面的例子來說就是 bindFunc
。
這時候你再用 call
或 apply
來呼叫也沒有用,this 值只會是一開始綁定的那個值,不能被改變。
一種快速判斷 this 值的技巧
在知道 call
怎麼使用以後,你就可以用這種角度來思考:
1 | 'use strict' |
透過這種把 function 前面的東西丟到 call
裡面,會幫助你更好判斷 this 值是什麼。不過還是要強調一下,這只是方便記憶,也許在 90% 的情境下是正確的,但不要忘了還是有 10% 的可能是錯的。
例外狀況
事件監聽器
在事件監聽下的 this 值會是被綁定的元素:
1 | <ul> |
這裡綁定的是 ul
,所以每當觸發 click 時,this 值就會是 ul
這個元素。(注意不是 e.target
)
箭頭函式
注意:這邊的範例是以 Node.js 為主,如果是瀏覽器的話結果可能不太一樣。
宣告的那個地方 this 值是什麼,出來就是什麼。
箭頭函式跟一般函式差別最大的地方就是這裡,它只在意「宣告」的地方,不會管「呼叫方式」。
1 | 'use strict' |
一個一個來看,首先在 obj 裡定義了 whatIsThis
來確認裡面的 this 值是什麼,得到的結果是 {}
。
接著呼叫 test1
,按照前面所說,一般函式的 this 值會根據呼叫的方式來決定 this 值,而這裡是透過 obj
來呼叫的,所以 this 值就是 obj
本身,符合推論。
再來是 test2
,也如同前面所說,箭頭函式的 this 值只管宣告的地方,所以宣告的地方是 obj 裡面,而 obj 裡的 this 值是 {}
,所以最後的結果確實是 {}
。
最後再出一題考考你,答得出來就代表你能分辨箭頭函式和一般函式的差別了:
1 | 'use strict' |