點燈坊

失くすものさえない今が強くなるチャンスよ

如何使 Function 存取自訂 Property ?

Sam Xiao's Avatar 2019-10-09

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

bind000

但結果為 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()

bind001

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()

bind002

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。

bind003

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