ECMAScript 可動態建立 Property,而 Function 本身亦是 Object,若想在 Function 內存取自己的 Property,直覺會使用 this
,但事實上並非如此。
Version
macOS Catalina 10.15
VS Code 1.38.1
Quokka 1.0.254
ECMAScript 5
ECMAScript 2015
this
let fn = function() {
this.count++;
};
fn.count = 0;
for(let i = 0; i < 5; i++) {
fn();
}
fn.count; // ?
第 1 行
let fn = function() {
this.count++;
};
定義 fn()
,因為 function 也是 object,因此想透過 this
存取其 count
property。
第 5 行
fn.count = 0;
對 fn()
新增 count
property 且初始值為 0
。
第 7 行
for(let i = 0; i < 5; i++) {
fn();
}
fn.count; // ?
for
loop 執行 fn()
5 次,預期結果為 5
。
但結果為 0
,why ?
fn()
雖為 function,也是 object,但並不代表 this
就是指向 object,別忘了只有當 function 為 object 的 method 時,this
才是指向 object;而 fn()
這種獨立 function,this
是指向 window
,因此 this.count++
實際上是對 window.count
做遞增,但因為 window.count
從來都沒定義過,所以是 undefined
,因此 this.count++
為 NaN
。
因為 fn.count
從來都沒有遞增過,一直都是 0
。
call()
let fn = function() {
this.count++;
};
fn.count = 0;
for(let i = 0; i < 5; i++) {
fn.call(fn);
}
fn.count; // ?
若想實作出預期結果的 5
呢 ?
fn()
最大問題是 this
指向 window
,若能使其指向 fn()
就好了。
第 8 行
fn.call(fn);
使用 Function.prototype.call()
傳入 fn
,強迫 this
指向 fn()
。
apply()
let fn = function() {
this.count++;
};
fn.count = 0;
for(let i = 0; i < 5; i++) {
fn.apply(fn);
}
fn.count; // ?
也可使用 Function.prototype.apply()
傳入 fn
,強迫 this
指向 fn()
。
call()
與 apply()
差異在於 call()
使用原本的 signature,而 apply()
改用 array,在此因為不需要傳入 argument,因此 call()
等效於 apply()
。
bind()
let fn = function() {
this.count++;
};
fn.count = 0;
for(let i = 0; i < 5; i++) {
fn.bind(fn)();
}
fn.count; // ?
也可使用 Array.prototype.bind()
將 fn
傳入,重新產生一個 this
指向 fn()
的 function。
Conclusion
this
為 dynamic scope,會隨著 context 而變,單獨 function 的this
指向window
,並不是 function 本身- 若要強迫
this
指向 function 自己,可以使用call()
、apply()
或bind()
,唯不可使用 arrow function,因為 arrow function 無法使用call()
、apply()
或bind()
改變this
,固定為 parent scope
Reference
許國政 (Kuro), 008 重新認識 JavaScript