前端入門學習筆記(二十八)JavaScript入門(十一)this丟失怎麼辦?——函數綁定

函數綁定

和對象方法或者和傳遞對象方法一起使用 setTimeout 時,有一個很常見的問題:“this 丟失”。

突然,this 就停止正常運作了。這種情況在開發的初學者中很典型,但有時也會出現在有經驗開發者的代碼中。


丟失 “this”

我們已經知道,在 JavaScript 中,this 很容易就會丟失。一旦一個方法被傳遞到另一個與對象分離的地方 —— this 就丟失了。

下面是使用 setTimeout 時 this 時如何丟失的:

let user = {
  firstName: "John",
  sayHi() {
    console.log(`Hello, ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // Hello, undefined!

正如我們看到的那樣,this.firstName 不是輸出爲 “John”,而是 undefined!

這是因爲 setTimeout 獲取到了函數 user.sayHi,但它和對象分離開了。最後一行可以寫爲:

let f = user.sayHi;
setTimeout(f, 1000); // 用戶上下文丟失

瀏覽器中的方法 setTimeout 有些特殊:它爲函數的調用設定了 this=window(對於 Node.JS,this 則會成爲時間對象,但其實 this 到底變成什麼並不十分重要)。所以對於 this.firstName 它其實試圖獲取的是 window.firstName,這個變量並不存在。在其他一些類似的情況下,通常 this 就會成爲 undefined。

這個需求很典型——我們希望將一個對象的方法傳遞到別的地方(這裏——是爲了調度程序)然後調用。如何確保它將會在正確的上下文中被調用呢?


解決方法:bind

原文中有兩種方案,一種是使用一個包裝函數,但在變量臨時改變的時,仍會出問題。
所以此處僅展示第二種解決方案bind
函數對象提供了一個內建方法 bind,它可以固定住 this。

基本的語法是:

// 稍後將會有更復雜的語法
let boundFunc = func.bind(context);

func.bind(context) 的結果是一個特殊的像函數一樣的“外來對象”,它可以像函數一樣被調用並且透明的將調用傳遞給 func 並設置 this=context。

換句話說,調用 boundFunc 就像是調用 func 並且固定住了 this。

舉個例子,這裏 funcUser 將調用傳遞給了 func 同時 this=user:

let user = {
  firstName: "John"
};

function func() {
  console.log(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John

這裏 func.bind(user) 作爲 func 的“邊界變量”,同時固定了 this=user。

所有的參數都會被傳遞給初始的 func,就像本來就是調用了它一樣,例如:

let user = {
  firstName: "John"
};

function func(phrase) {
  console.log(phrase + ', ' + this.firstName);
}

// 將 this 綁定給 user
let funcUser = func.bind(user);

funcUser("Hello"); // Hello, John(參數 "Hello" 被傳遞了,並且 this=user)

下面我們來嘗試一個對象的方法:

let user = {
  firstName: "John",
  sayHi() {
    console.log(`Hello, ${this.firstName}!`);
  }
};

let sayHi = user.sayHi.bind(user); // (*)

sayHi(); // Hello, John!

setTimeout(sayHi, 1000); // Hello, John!

在 (*) 之間的行中,我們取得了方法 user.sayHi 然後將它和 user 綁定。sayHi 是一個“邊界”方法,它可以單獨調用或者傳遞給 setTimeout —— 都沒關係,函數上下文都將會是正確的。

這裏我們能夠看到參數都被像正常調用原函數一樣被傳遞了進去,但是 this 被 bind 方法固定了:

 let user = {
  firstName: "John",
  say(phrase) {
    console.log(`${phrase}, ${this.firstName}!`);
  }
};

let say = user.say.bind(user);

say("Hello"); // Hello, John ("Hello" 參數被傳遞給了函數 say)
say("Bye"); // Bye, John ("Bye" 被傳遞給了函數 say)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章