this 是 JavaScript 的一個關鍵字, this 會在不同的情境下指稱到不同的物件。
this 在物件導向裡面,它所代表的就是那個 instance 本身:
1 | class Car { |
在上面我們宣告了一個 class Car
,寫了 setName 跟 getName 兩個方法,在裡面用this.name
來取放這個 instance 的屬性,myCar.setName('Ford')
,所以 this 就會是myCar
。
this 不等於 function
如果直接調用函式,此函式的 this 會指向 window 。
1 | window.say = 'Hi'; |
this
不會指到 CallSay
這個 function,實際上是指向 window
。
再看一個範例:
1 | var call = function() { |
在這個範例中, say
可以透過 this.call
取得 call
,是因為 this.call
實際上是指向 window.call
。
而 call
的 this.name
並非是 say
中的 Felix
,而是指向 window.name
,所以會得到 undefined
的結果。
下個例子是將 function 內在包覆著 function,但只要是直接呼叫,this 都是屬於全域。
1 | window.say = 'Hi'; |
來看一下巢狀迴圈中的 this :
1 | var obj = { |
在 obj.func1()
裡面的 this
會指向 func1
;是因為 func1
透過 obj
來呼叫的。
但 obj.func1()
裡面的 func2()
在執行時的 this
卻會指向 window
。
也就是當沒有特定指明 this 的情況下,預設綁定 (Default Binding) this 為 「全域物件」,也就是 window。
以上這幾種情況, this 的值在瀏覽器底下就會是 window
,在 node.js 底下會是 global
,如果是在嚴格模式,this 的值就會是 undefined
。
強制指定 this 的方式
在 JavaScript 有三個可以強制指定 this
的方式,分別是 call()
、 apply()
以及 bind()
。
call
跟 apply
是很類似的方法,這兩種都是能夠呼叫 fucntion 的函式
1 | ; |
因為是嚴格模式所以 funA(1, 2)
的 this 是 undefined
。
call
和 apply
就是你第一個參數傳什麼,裡面 this 的值就會是什麼。儘管原本已經有 this,也依然會被覆蓋:
而兩者的差別只在於 apply
傳進去的參數是一個 array,所以上面這三種呼叫 function 的方式是等價的。除了直接呼叫 function 以外,你也可以用 call 或是 apply 去呼叫,差別在於傳參數的方式不同。
除了以上兩種以外,還有最後一種可以改變 this 的方法:bind。
1 | ; |
在這邊我們把 funA
這個 function 用 Hi
來綁定,所以最後呼叫 myFunA
時會輸出 Hi
。
在看一個例子:
1 | var obj = { |
加上了 bind
之後的 func.bind(obj)()
,會替我們將 func
的 this
暫時指向我們所設定的 obj
。
於是 console.log(this.LastName)
的結果自然就是 obj.LastName
也就是 Lincoln
了。
重新指向 this
假設我們今天在某個元素上透過 addEventListener
註冊了 click
事件,而在事件中的 this
指的是「觸發事件的元素」。
要是我們在事件的 callback function 加入 ajax 的請求,那麼根據前面所說的,預設綁定 (Default Binding) 會把這個 callback function 的 this
指定給 global object
,也就是 window
。
如果需調用的則是物件本身的話,可以先用一個變數指向 this
,等到調用後再重新使用它。
1 | el.addEventListener("click", function(event) { |
像這樣,我們將事件內的 this
先用一個叫 that
的變數儲存它的參考,那麼在 ajax 的 callback function 就可以透過 that
來存取到原本事件中的 this
了。
如果我們把 call 跟 bind 同時用會怎樣!?
1 | ; |
答案是不會改變,一但 bind 了以後值就不會改變了。
在非嚴格模式底下,無論是用 call、apply 還是 bind,你傳進去的如果是基本型別都會被轉成 object 。
舉例來說:
1 | function funA() { |
物件中的 this
最前面我們示範了在物件導向 class 裡面的 this,但在 JavaScript 裡面還有另外一種方式也是物件:
1 | const obj = { |
這種跟一開始的物件導向範例不太一樣,這個範例是直接創造了一個物件而沒有透過 class,所以你也不會看到 new 這個關鍵字的存在。
舉個簡單的例子來幫大家複習一下 Scope Chain:
1 | var value = 1 |
無論怎麼呼叫 say
這個 function,印出來的 value
永遠都會是全域變數的 value
,因為 say
在自己的作用域底下找不到 value
於是往上一層找,就會找到 global scope ,這跟你在哪裡呼叫 say
一點關係都沒有。 say
這個 function 在「定義」的時候就把 scope 給決定好了。
但 this 卻是完全相反,this 的值會根據你怎麼呼叫它而變得不一樣,像是先前我們剛講過的 call、apply 跟 bind ,你可以用不同的方式去呼叫 function,讓 this 的值變得不同。
this 的值跟作用域跟程式碼的位置在哪裡完全無關,只跟「如何呼叫」有關。
讓我們看複雜一點的例子:
1 | const obj = { |
say
因為沒有傳參數進去,所以是預設綁定,在非嚴格模式底下是 window
,所以會 window.value
也就是 undefined
。
可以透過把 function 的呼叫轉成用 call 的形式,就會較容易看出 this 的值是什麼。
1 | obj.func2.say() // obj.func2.say.call(obj.func2) => 2 |
箭頭函式的 this
從 ES6 開始新增了一種叫做 「箭頭函式表示式」 (Arrow Function expression) 的函式表達式。
而箭頭函式有兩個重要的特性:
- 更簡短的函式寫法
- this 變數強制綁定
ES6 新增的箭頭函式中的 this
有不一樣的運作方式,只要記住「在宣告它的地方的 this 是什麼,它的 this 就是什麼」,什麼意思呢?讓我們看個範例:
1 | const obj = { |
我們在 say
這個 function 裡面宣告了 func1
這個箭頭函式,所以 say
的 this 是什麼, func1
的 this 就會是什麼。
箭頭函式的 this 不是取決於在宣告時那個地方的 this。