人們很容易把 this 理解成指向函數(shù)自身凫乖,這個推斷從英語的語法角度來說是說得通的朴译。但是首先需要消除一些關(guān)于 this 的錯誤認(rèn)識。太拘泥于“this”的字面意思就會產(chǎn)生一些誤解。有兩種常見的對于 this 的解釋,但是它 們都是錯誤的增淹。
- 默認(rèn)綁定
思考一下下面這段代碼
function foo () {
console.log(this.a);
}
var a = 2;
foo();
// 2
在上面的例子中this.a解析為了全局變量中的a,因?yàn)樵诒?例中乌企,函數(shù)調(diào)用時應(yīng)用了 this 的默認(rèn)綁定虑润,因此 this 指向全局對象〖咏停可以通過分析調(diào)用位置來看看 foo() 是如何調(diào) 用的拳喻。在代碼中,foo() 是直接使用不帶任何修飾的函數(shù)引用進(jìn)行調(diào)用的猪腕,因此只能使用 默認(rèn)綁定冗澈,無法應(yīng)用其他規(guī)則。
如果使用嚴(yán)格模式(strict mode)陋葡,那么全局對象將無法使用默認(rèn)綁定亚亲,因此this會綁定 到 undefined:
function foo() {
'use strict'
console.log(this.a);
}
var a = 2;
foo();
// TypeError: this is undefined
這里有一個微妙但是非常重要的細(xì)節(jié),雖然 this 的綁定規(guī)則完全取決于調(diào)用位置腐缤,但是只 有foo()運(yùn)行在非strict mode下時捌归,默認(rèn)綁定才能綁定到全局對象;嚴(yán)格模式下與foo() 的調(diào)用位置無關(guān):
function foo() { console.log( this.a );
}
var a = 2;
(function(){ "use strict";
foo(); // 2
})();
通常來說你不應(yīng)該在代碼中混合使用strict mode和non-strict mode。整個 程序要么嚴(yán)格要么非嚴(yán)格岭粤。然而陨溅,有時候你可能會用到第三方庫,其嚴(yán)格程 度和你的代碼有所不同绍在,因此一定要注意這類兼容性細(xì)節(jié)。
- 隱式綁定
另一條需要考慮的規(guī)則是調(diào)用位置是否有上下文對象雹有,或者說是否被某個對象擁有或者包
含偿渡,不過這種說法可能會造成一些誤導(dǎo)。
思考下面的代碼:
function foo() {
console.log( this.a );
}
var obj={
a: 2,
foo: foo
};
obj.foo();
// 2
首先需要注意的是 foo() 的聲明方式霸奕,及其之后是如何被當(dāng)作引用屬性添加到 obj 中的溜宽。 但是無論是直接在 obj 中定義還是先定義再添加為引用屬性,這個函數(shù)嚴(yán)格來說都不屬于 obj 對象质帅。
然而适揉,調(diào)用位置會使用 obj 上下文來引用函數(shù)留攒,因此你可以說函數(shù)被調(diào)用時 obj 對象“擁 有”或者“包含”它。
無論你如何稱呼這個模式嫉嘀,當(dāng) foo() 被調(diào)用時炼邀,它的落腳點(diǎn)確實(shí)指向 obj 對象。當(dāng)函數(shù)引 用有上下文對象時剪侮,隱式綁定規(guī)則會把函數(shù)調(diào)用中的 this 綁定到這個上下文對象拭宁。因?yàn)檎{(diào) 用 foo() 時 this 被綁定到 obj,因此 this.a 和 obj.a 是一樣的瓣俯。
對象屬性引用鏈中只有最頂層或者說最后一層會影響調(diào)用位置杰标。舉例來說:
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo();
// 42
隱式丟失
一個最常見的 this 綁定問題就是被隱式綁定的函數(shù)會丟失綁定對象,也就是說它會應(yīng)用默認(rèn)綁定彩匕,從而把 this 綁定到全局對象或者 undefined 上腔剂,取決于是否是嚴(yán)格模式。 思考下面的代碼:
function foo() {
console.log( this.a );
}
var obj={
a: 2,
foo: foo
};
var bar = obj.foo;
// 函數(shù)別名!
var a = "oops, global";
// a 是全局對象的屬性 bar(); // "oops, global"
參數(shù)傳遞其實(shí)就是一種隱式賦值驼仪,因此我們傳入函數(shù)時也會被隱式賦值掸犬,所以結(jié)果和上一 個例子一樣。
- 顯式綁定
思考下面的代碼:
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2
通過 foo.call(..)谅畅,我們可以在調(diào)用 foo 時強(qiáng)制把它的 this 綁定到 obj 上登渣。
如果你傳入了一個原始值(字符串類型、布爾類型或者數(shù)字類型)來當(dāng)作 this 的綁定對 象毡泻,這個原始值會被轉(zhuǎn)換成它的對象形式(也就是new String(..)胜茧、new Boolean(..)或者 new Number(..))。這通常被稱為“裝箱”仇味。
- 硬綁定
但是顯式綁定的一個變種可以解決這個問題呻顽。
思考下面的代碼:
function foo () {
console.log(this.a);
}
var obj = {
a: 2
}
var bar = function () {
foo.call(obj);
}
bar();
setTimeout(bar, 100); // 2
// 硬綁定的 bar 不可能再修改它的
this bar.call( window ); // 2
我們來看看這個變種到底是怎樣工作的。我們創(chuàng)建了函數(shù) bar()丹墨,并在它的內(nèi)部手動調(diào)用 了 foo.call(obj)廊遍,因此強(qiáng)制把 foo 的 this 綁定到了 obj。無論之后如何調(diào)用函數(shù) bar贩挣,它 總會手動在 obj 上調(diào)用 foo喉前。這種綁定是一種顯式的強(qiáng)制綁定,因此我們稱之為硬綁定
- new綁定
這是第四條也是最后一條 this 的綁定規(guī)則王财,在講解它之前我們首先需要澄清一個非常常見
的關(guān)于 JavaScript 中函數(shù)和對象的誤解卵迂。 在傳統(tǒng)的面向類的語言中,“構(gòu)造函數(shù)”是類中的一些特殊方法绒净,使用 new 初始化類時會
調(diào)用類中的構(gòu)造函數(shù)见咒。通常的形式是這樣的:
something = new MyClass(..);
JavaScript 也有一個 new 操作符,使用方法看起來也和那些面向類的語言一樣挂疆,絕大多數(shù)開 發(fā)者都認(rèn)為 JavaScript 中 new 的機(jī)制也和那些語言一樣改览。然而下翎,JavaScript 中 new 的機(jī)制實(shí) 際上和面向類的語言完全不同。
首先我們重新定義一下 JavaScript 中的“構(gòu)造函數(shù)”宝当。在 JavaScript 中视事,構(gòu)造函數(shù)只是一些 使用 new 操作符時被調(diào)用的函數(shù)。它們并不會屬于某個類今妄,也不會實(shí)例化一個類郑口。實(shí)際上, 它們甚至都不能說是一種特殊的函數(shù)類型盾鳞,它們只是被 new 操作符調(diào)用的普通函數(shù)而已犬性。
舉例來說,思考一下 Number(..) 作為構(gòu)造函數(shù)時的行為腾仅,ES5.1 中這樣描述它:Number 構(gòu)造函數(shù)
當(dāng) Number 在 new 表達(dá)式中被調(diào)用時乒裆,它是一個構(gòu)造函數(shù):它會初始化新創(chuàng)建的 對象。
使用 new 來調(diào)用函數(shù)推励,或者說發(fā)生構(gòu)造函數(shù)調(diào)用時鹤耍,會自動執(zhí)行下面的操作。
- 創(chuàng)建(或者說構(gòu)造)一個全新的對象验辞。
- 這個新對象會被執(zhí)行[[原型]]連接稿黄。
- 這個新對象會綁定到函數(shù)調(diào)用的this。
- 如果函數(shù)沒有返回其他對象跌造,那么new表達(dá)式中的函數(shù)調(diào)用會自動返回這個新對象杆怕。
我們現(xiàn)在關(guān)心的是第 1 步、第 3 步壳贪、第 4 步陵珍,所以暫時跳過第 2 步,第 5 章會詳細(xì)介紹它违施。 思考下面的代碼:
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
使用 new 來調(diào)用 foo(..) 時互纯,我們會構(gòu)造一個新對象并把它綁定到 foo(..) 調(diào)用中的 this 上。new 是最后一種可以影響函數(shù)調(diào)用時 this 綁定行為的方法磕蒲,我們稱之為 new 綁定留潦。
優(yōu)先級
new > 顯示綁定 > 隱性綁定 >默認(rèn)綁定
小結(jié)
如果要判斷一個運(yùn)行中函數(shù)的 this 綁定,就需要找到這個函數(shù)的直接調(diào)用位置辣往。找到之后
就可以順序應(yīng)用下面這四條規(guī)則來判斷 this 的綁定對象愤兵。
- 由new調(diào)用?綁定到新創(chuàng)建的對象。
- 由call或者apply(或者bind)調(diào)用?綁定到指定的對象排吴。
- 由上下文對象調(diào)用?綁定到那個上下文對象。
- 默認(rèn):在嚴(yán)格模式下綁定到undefined懦鼠,否則綁定到全局對象钻哩。
一定要注意屹堰,有些調(diào)用可能在無意中使用默認(rèn)綁定規(guī)則。如果想“更安全”地忽略 this 綁 定街氢,你可以使用一個 DMZ 對象扯键,比如 ? = Object.create(null),以保護(hù)全局對象珊肃。
ES6 中的箭頭函數(shù)并不會使用四條標(biāo)準(zhǔn)的綁定規(guī)則荣刑,而是根據(jù)當(dāng)前的詞法作用域來決定 this,具體來說伦乔,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的 this 綁定(無論 this 綁定到什么)厉亏。這 其實(shí)和 ES6 之前代碼中的 self = this 機(jī)制一樣。