函數綁定
在前面我們已經知道setTimeout()很容易就會丟失this,看下面的例子:
let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(user.sayHi, 1000); // Hello, undefined!
這裡this.firstName的值為undefined,因為setTimeout()在接收user.sayHi()時與user對象是隔離的,故this就丟失瞭。它類似於一下的操作:
let f = user.sayHi; setTimeout(f, 1000); // lost user context
因為在瀏覽器中運行,所以丟失瞭上下文對象user後,this所指向的對象就是全局對象window,故為undefined
解決方案
(1)使用包裝器
let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(function() { user.sayHi(); // Hello, John! }, 1000);
setTimeout的第一個參數直接使用user.sayHi(),此時setTimeout就會根據詞法環境接受user對象作為上下文對象,下面是簡寫的例子:
setTimeout(() => user.sayHi(), 1000); // Hello, John!
但是有一個問題要註意的是,如果setTimeout要調用的執行函數內容在調度前被修改,那麼setTimeout觸發的執行函數為修改過的內容,例如:
let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(() => user.sayHi(), 1000); // ...within 1 second user = { sayHi() { alert("Another user in setTimeout!"); } }; // Another user in setTimeout?!?
為瞭結果這個問題,我們需要用bind()來綁定上下文對象
(2)bind()
Javascript的函數都內置瞭bind()方法來綁定上下文對象,它的語法如下:
// more complex syntax will be little later let boundFunc = func.bind(context);
bind()默認返回修改過上下文對象(this=context)的新函數(boundFunc)
看下面的例子:
let user = { firstName: "John" }; function func(phrase) { alert(phrase + ', ' + this.firstName); } // bind this to user let funcUser = func.bind(user); funcUser("Hello"); // Hello, John (argument "Hello" is passed, and this=user)
上述例子中,當我們調用funcUser(…)的時候,它會去調用func()並且修改上下文對象this=context
現在我們嘗試綁定對象方法,例如:
let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; let sayHi = user.sayHi.bind(user); // (*) sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John!
如果一個對象有許多的方法需要綁定上下文對象,我們可以使用bindAll()來綁定所有的方法,或者我們可以遍歷對象的所有屬性方法來綁定this,例如:
for (let key in user) { if (typeof user[key] == 'function') { user[key] = user[key].bind(user); } }