this
物件:視乎怎麼呼叫 Callback Function。
JavaScript 裡常會需要用到 Callback Function,將某個函數物件 A 當作參數傳進另一個函數 B,由函數 B 決定執行 A 的時機。
這時候就要注意函數 B 是如何去呼叫函數 A,否則函數 A —— 也就是 Callback Function —— 裡面的 this
,很可能不是你所預期的對象。
今天我有一個 hero
物件,存在一些屬性,例如 name
,我想透過 Callback 的方式去控制 hero
物件,我只在 hero
物件裡實作一個 act()
,負責執行 Callback Function。而 Callback Function 的內容由其他人提供。例如以下:
var name = "Hi I am Global";
function sayHi(){
return this.name;
}
var hero = {
name: "Hi I am a Hero",
act: function(cbk){
return cbk();
}
};
console.log( sayHi() ); // Hi I am Global
console.log( hero.act(sayHi) ); // Hi I am Global
hero.act(sayHi)
就是一個 callback 的用法,sayHi
就是 callback function。hero.act()
裡,採用簡單呼叫的方式來執行 callback function。this
會是 Global 物件,所以 hero.act(sayHi)
回傳的結果是 Global 變數的 name
。"use strict";
var name = "Hi I am Global";
function sayHi(){
return this.name;
}
var hero = {
name: "Hi I am a Hero",
act: function(cbk){
return cbk();
}
};
console.log( sayHi() ); // TypeError: Cannot read property 'name' of undefined
console.log( hero.act(sayHi) ); // TypeError: Cannot read property 'name' of undefined
this
會是 undefined
,無法執行 this.name
,會發生錯誤。但上述的範例,我想達到的效果是 hero.act(sayHi)
可以回傳物件 hero
自己的 name
值,我該怎麼做?
如果想把物件本身帶入 Callback Function 裡的 this
,就要用 apply()
/ call()
。
var name = "Hi I am Global";
function sayHi(){
return this.name;
}
var hero = {
name: "Hi I am a Hero",
act: function(cbk){
return cbk.apply(this); // 將物件本身傳入 Callback Function
}
};
console.log( sayHi() ); // Hi I am Global
console.log( hero.act(sayHi) ); // Hi I am a Hero
this
出現的位置有可能在:
但全域執行環境下 (Global Context) 遇到的機率相對低,而且十分單純,this
就是 Global 物件本身。
比較容易發生問題的是函數執行環境 (Function Context),遇到的情境可能千變萬化,過程可能讓人很混淆。
記得一個大原則:看呼叫時的物件是誰。
JavaScript 的 this
不是看定義的語彙位置,而是根據執行當下誰擁有這段程式碼,也就是看誰呼叫的。
但要注意的是,簡單呼叫 (Simple Call) 的情形下,一般模式和嚴謹模式會有不同的行為。
所有人都知道,寫程式就像學數學一樣,不建議背誦,而是去理解背後原理 (話說回來,比樂透可能性還多的語法組合,企圖用背的也很不科學)。
但當你確定你已經理解原理之後,我不反對用一些類似口訣或公式筆記的方式來輔助記憶,幫助快速回憶。畢竟如果每次寫程式要用到時,都從頭推導,那效率是不現實的。
曾有人做過實驗,讓大學數學教授和高中生一起做同一份高中數學考券,結果高中生輕輕鬆鬆在時間內完成,數學教授卻沒有寫完。是因為數學教授的程度比高中生差嗎?
當然不是。
數學教授每一個題目都知道背後原理,給他足夠的時間推導,他每一題都能完美解答。但考試時間是有限的,對那些高中生來說,這些題目範圍是他們非常熟悉,幾乎看到題目腦海就浮現解法。這代表那些高中生不理解背後原理嗎?不,他們也是經過理解的過程學到這些題目的解法,但為了應付嚴峻的考試,他們用各種方法來加速遇到題目的解決速度,例如解題口訣、公式表。
現實中的專案開發也有同樣情境。專案開發通常有時程限制,為了減少每次回憶的時間,相信很多工程師都有自己的私藏筆記,這些筆記就是你理解的精華,用來幫助自己快速回想,或減少從頭推導的時間。
前面舉了非常多情境,整體歸納下來,大致上不出以下公式的範圍:
呼叫方式 | 模式 | this 所指的物件 |
---|---|---|
obj.method() | 不限 | 該 obj 物件 |
function() | 一般模式 | Global 物件 |
function() | 嚴謹模式 | undefined |
透過 apply() 或 call() | 不限 | 第一個參數的物件 (若第一個參數是 null ,則視同「function()」) |
bind()
綁定而來的函數物件bind()
產生的函數,不管呼叫方式為何,this
都指向當初 bind()
所綁定的物件。this
不是變數這一點我想大家都清楚,只是作為 this
介紹完整性的一個小補充。
正如最一開始所說,this
是一個關鍵字,不是變數,所以不能改變 this 的值,例如企圖這樣:
this = { name: "OneJar" };