原文首發(fā)于 baishusama.github.io瓢谢,歡迎圍觀~
概述
本文主要講述了幾種場景下 setTimeout
的回調(diào)函數(shù)的 this
綁定出錯的幾種解決方法。
舉個栗子
class Person {
sayName() {
console.log("Hello, I'm " + this.name + ".")
}
constructor(name = "No One") {
this.name = name
setTimeout(this.sayName, 1000)
}
}
var imo = new Person("Imo")
setTimeout(imo.sayName, 2000)
P.S. 上述代碼使用了 ES6 語法榴都,這里不做語法上的過多解釋赏廓。不知所謂的童鞋可以看一下《30 分鐘掌握 ES6 核心內(nèi)容(上)》械拍、《30 分鐘掌握 ES6 核心內(nèi)容(下)》這兩篇滨嘱。
上述代碼定義了一個 Person
類拉庵,這個類有一個構(gòu)造函數(shù)和一個 sayName
方法灿椅。其中,sayName
方法內(nèi)部使用了 this
關(guān)鍵字钞支,用于引用當(dāng)前實例的(公有成員)變量 name
茫蛹。
上述代碼中,有兩個 setTimeout
函數(shù)烁挟。第一個 setTimeout
存在于 Person
類的構(gòu)造函數(shù)當(dāng)中婴洼,其第一個參數(shù)是 this.sayName
;第二個存在于全局作用域中撼嗓,其第一個參數(shù)是 imo.sayName
柬采。兩者都期望在控制臺打印 Hello, I'm Imo.
欢唾,但是目前兩者都沒有達到期望——目前打印的均是 Hello, I'm .
。出現(xiàn)這種現(xiàn)象的原因是 sayName
方法內(nèi)部的 this
沒有如期地指向 imo
對象粉捻,而是錯誤地指向了全局對象礁遣,因而 this.name
的值為 undefined
,對應(yīng)到字符串是 ''
(空字符串)肩刃。
那么祟霍,我們?nèi)绾涡薷拇a使得 this
關(guān)鍵字如期地指向 imo
對象呢?
第二個 setTimeout
先解決比較簡單的第二個 setTimeout
盈包。
有兩種方法:
.bind()
- 匿名函數(shù)包裹
代碼如下:
// 第二個 setTimeout 用“.bind()”方法修改如下:
setTimeout(imo.sayName.bind(imo), 2000)
// 第二個 setTimeout 用“匿名函數(shù)包裹”方法修改如下:
setTimeout(function(){
imo.sayName()
}, 2000)
“.bind()
”方法
Apply 調(diào)用模式 - The Apply Invocation Pattern
Function.prototype.bind() @MDN:“bind()
方法會創(chuàng)建一個新函數(shù)沸呐。當(dāng)這個新函數(shù)被調(diào)用時,bind()
的第一個參數(shù)將作為它運行時的 this
...”
“匿名函數(shù)包裹”方法
方法調(diào)用模式 - The Method Invocation Pattern
在外層匿名函數(shù)的內(nèi)部呢燥,sayName
作為 imo
對象的方法被調(diào)用崭添,方法內(nèi)部的 this
也就被綁定到 imo
對象了。
第一個 setTimeout
我們嘗試上面提到的兩種解決方法叛氨。
“.bind()
”方法
.bind()
方法依舊可行呼渣,代碼:
// 第一個 setTimeout 用“.bind()”方法修改如下:
setTimeout(this.sayName.bind(this), 1000)
“匿名函數(shù)包裹”方法
“匿名函數(shù)包裹”方法好像讓事情更復(fù)雜了——修改后但沒能解決問題的代碼如下:
// 第二個 setTimeout 用“匿名函數(shù)包裹”方法修改如下:
setTimeout(function(){
this.sayName()
}, 1000)
此時,this.sayName
的值為 undefined
力试,sayName
方法甚至都沒有機會得到調(diào)用徙邻。
“匿名函數(shù)包裹+.bind
”
在包裹匿名函數(shù)后的代碼的基礎(chǔ)上,仍可以使用 .bind
方法畸裳。
// 第二個 setTimeout 用“匿名函數(shù)包裹+.bind”方法修改如下:
setTimeout(function(){
this.sayName()
}.bind(this), 1000)
“匿名函數(shù)包裹+that
”
可以利用匿名函數(shù)形成閉包缰犁,引用正確的 this
:
// 第二個 setTimeout 用“匿名函數(shù)包裹+that”方法修改如下:
var that = this
setTimeout(function(){
that.sayName()
}, 1000)
“箭頭函數(shù)”方法
ES6 的箭頭函數(shù)沒有自己的 this
、直接繼承外部作用域的 this
怖糊,所以還可以這么改:
// 第二個 setTimeout 用“箭頭函數(shù)”方法修改如下:
setTimeout(()=>{this.sayName()}, 1000)
小結(jié)
當(dāng) setTimeout
的回調(diào)函數(shù)中出現(xiàn) this
的時候帅容,要特別注意其綁定的對象是否和預(yù)想的一致。當(dāng)綁定有誤時可以通過下述方法解決伍伤。
當(dāng) setTimeout
的回調(diào)函數(shù)是一個能夠通過變量引用的對象的方法(類似于例子中的 imo.sayName
)時并徘,有兩種解決方法:
- “
.bind()
”方法 - “匿名函數(shù)包裹”方法
當(dāng) setTimeout
的回調(diào)函數(shù)是一個通過 this
訪問的方法(類似于例子中的 this.sayName
)時,有兩種解決方法:
- “
.bind()
”方法 - “ES6箭頭函數(shù)”方法
當(dāng) setTimeout
的回調(diào)函數(shù)是一個含有 this
的匿名函數(shù)(類似于例子中的 this.sayName
被匿名函數(shù)包裹后)時扰魂,有兩種解決方法:
- “
.bind()
”方法 - “閉包that”方法
可以看出麦乞,解決方法中的“
.bind()
”方法是萬金油。所以劝评,如果你記不住這么多方法姐直,至少也要記住“.bind()
”方法。