現(xiàn)在了解了函數(shù)調(diào)用中this綁定的四條規(guī)則,需要做的是找到函數(shù)的調(diào)用位置并判斷應(yīng)用了哪條規(guī)則船惨。如果調(diào)用位置應(yīng)用多條規(guī)則,就必須給這些規(guī)則設(shè)定優(yōu)先級缕陕。
毫無疑問粱锐,默認(rèn)綁定是四條規(guī)則中最低的,可以暫時先不考慮扛邑。
測試下隱式綁定和顯式綁定優(yōu)先級:
function foo() {
console.log(this.a);
}
var obj1 = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
foo: foo
}
obj1.foo() // 1
obj2.foo() // 2
obj1.foo.call(obj2) // 2
obj2.foo.call(obj1) // 1
顯式綁定優(yōu)先級更高怜浅,也就是說在判斷時應(yīng)當(dāng)先考慮是否可以應(yīng)用顯式綁定。
測試下new綁定和隱式綁定優(yōu)先級:
function foo(something) {
this.a = something
}
var obj1 = {
foo: foo
}
var obj2 = {}
obj1.foo(2)
console.log(obj1.a) // 2
obj1.foo.call(obj2, 3)
console.log(obj2.a) // 3
var bar = new obj1.foo(4) // new 和 call/apply 無法一起使用蔬崩,因此無法通過new foo.call(obj1)來直接測試
console.log(obj1.a) // 2
console.log(bar.a) // 4
new綁定比隱式綁定優(yōu)先級高恶座。接下來就是比較new綁定和顯式綁定?
在看代碼之前沥阳,先回憶下硬綁定是如何工作的跨琳。Function.prototype.bind(...)會創(chuàng)建一個新的包裝函數(shù),這個函數(shù)會忽略當(dāng)前的this綁定(無論綁定的對象時什么)桐罕,并把提供的對象綁定到this上脉让。
這樣看起來硬綁定(也是顯式綁定的一種)似乎b比new綁定的優(yōu)先級更高桂敛。
function foo(something) {
this.a = something
}
var obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) // 2
var baz = new bar(3)
console.log(obj1.a) // 2
console.log(baz.a) // 3
bar被硬綁定到了obj1上,但是new bar(3)并沒有向我們預(yù)計的那樣把obj1.a的值修改為3溅潜。相反术唬,new修改了硬綁定(到obj1的)調(diào)用bar(...)中的this。因此使用了new綁定滚澜,我們得到了一個名字為baz的新對象粗仓,并且baz.a的值是3。
判斷this
如果要判斷一個運行中函數(shù)this的綁定设捐,那么就需要找到這個函數(shù)的直接調(diào)用位置潦牛。找到之后按照以下規(guī)則判斷this綁定的對象。
- 1.函數(shù)是否在new中調(diào)用(new綁定)挡育?如果是this綁定的是新創(chuàng)建的對象
var bar = new foo() - 2.函數(shù)是否通過call、apply(顯式綁定)或者硬綁定調(diào)用朴爬?如果是this綁定的是指定的對象
var bar = foo.call(obj1) - 3.函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定)即寒?如果是this綁定的是那個上下文對象
var bar = obj1.foo() - 4.如果都不是,使用默認(rèn)綁定召噩。如果在嚴(yán)格模式下母赵,就綁定到了undefined,否則綁定到了全局對象具滴。
var bar = foo()
綁定例外
1.被忽略的this
如果你把null或者undefined作為this的綁定對象傳入到call/apply/bind凹嘲,這些值在調(diào)用時會被忽略,實際應(yīng)用的會是默認(rèn)綁定
function foo() {
console.log(this.a)
}
var a = 2;
foo.call(null) // 2
什么情況下你會傳入null构韵?
一種非常常見的做法是使用apply(...)來“展開”一個數(shù)組周蹭,并當(dāng)做參數(shù)傳入一個函數(shù)。類似的疲恢,bind(...)可以對參數(shù)進行kelihua柯里化(預(yù)先設(shè)置一些參數(shù))凶朗,非常有用:
function foo(a, b) {
console.log("a: " + a + ", b:" + b)
}
// 把數(shù)組展開成參數(shù)
foo.apply(null, [2, 3]) // a: 2, b:3
// 使用bind(...)進行柯里化
var baz = foo.bind(null, 4)
baz(5) //a: 4, b:5
這兩種方法都需要傳入一個參數(shù)當(dāng)做this的綁定對象。如果函數(shù)不關(guān)心this的話显拳,你仍然需要傳入一個占位值棚愤,這時null就是個不錯的選擇。
使用null帶來的副作用是杂数,如果某個函數(shù)確實使用了this宛畦,那么默認(rèn)綁定會把this綁定到全局對象中。(可以使用Object.create(null)來代替null更安全)
2.間接引用
需要注意的是揍移,可能創(chuàng)建一個函數(shù)的間接引用次和,這種情況下會應(yīng)用默認(rèn)綁定
function foo() {
console.log(this.a)
}
var a = 2;
var o = {a: 3, foo: foo}
var p = {a: 4}
o.foo(); //3
(p.foo = o.foo)(); // 4
賦值表達(dá)式p.foo = o.foo的返回值是目標(biāo)函數(shù)的引用,因此調(diào)用位置是foo()而不是p.foo()或者o.foo()那伐,所以會應(yīng)用默認(rèn)綁定斯够。
注意:對于默認(rèn)綁定來說囚玫,決定this綁定對象的并不是調(diào)用位置是否處于嚴(yán)格模式,而是函數(shù)體是否處于嚴(yán)格模式读规,嚴(yán)格模式下this綁定到undefined抓督,否則this綁定到全局對象
3.軟綁定
this詞法
ES6中介紹了一種無法使用這些規(guī)則的特殊函數(shù):箭頭函數(shù)。
箭頭函數(shù)并不是使用function定義的束亏,箭頭函數(shù)不適用這四種標(biāo)準(zhǔn)規(guī)則铃在,而是根據(jù)外層作用域來決定this。(箭頭函數(shù)會繼承外層函數(shù)調(diào)用的this綁定)
function foo() {
return (a) => {
// this繼承自foo
console.log(this.a)
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1)
bar.call(obj2) // 2
foo內(nèi)部創(chuàng)建的箭頭函數(shù)會捕獲調(diào)用foo()的this碍遍,由于foo()的this綁定到的是obj1定铜,bar(引用箭頭函數(shù))的this也會綁定到obj1,箭頭函數(shù)的綁定無法修改(new也不行)
箭頭函數(shù)最常用于回調(diào)函數(shù)中怕敬,例如事件處理器或者定時器:
function foo() {
setTimeout(() => {
// 這里的this在詞法上繼承自foo()
console.log(this.a)
}, 100)
}
var obj = {
a: 2
}
foo.call(obj) // 2
箭頭函數(shù)可以像bind(...)一樣確保函數(shù)的this被綁定到指定對象揣炕。此外,其重要性還體現(xiàn)在它用更常見的詞法作用域取代了傳統(tǒng)的this機制东跪。實際上畸陡,ES6之前我們就已經(jīng)在使用一種幾乎和箭頭函數(shù)一樣模式
function foo() {
var self = this;
setTimeout(function() {
// 這里的this在詞法上繼承自foo()
console.log(self.a)
}, 100)
}
var obj = {
a: 2
}
foo.call(obj) // 2
雖然self = this和箭頭函數(shù)看起來都可以取代bind(...),但從本質(zhì)上來說他們想取代的是this機制。
如果你經(jīng)常編寫this風(fēng)格的代碼虽填,但絕大部分都會使用self = this或者箭頭函數(shù)來否定this機制丁恭,那你或許應(yīng)當(dāng):
- 1.只使用詞法作用域并完全拋棄this風(fēng)格代碼
- 2.完全采取this風(fēng)格,在必要時使用bind(...),盡量避免self=this和箭頭函數(shù)斋日。