現(xiàn)象
首先我們來看一段代碼
let a = 2;
let obj = {
a: 1,
foo: function () { console.log(this.a) }
};
let foo = obj.foo;
// 寫法一
obj.foo()
// 寫法二
foo()
寫法一和寫法二的執(zhí)行結(jié)果分別是:1伍宦、2芽死。之所以運行結(jié)果不用,是因為方法中使用了 this 關(guān)鍵字次洼。this 指代方法調(diào)用者关贵,即運行時的環(huán)境。對于obj.foo()卖毁,foo 運行在 obj 中揖曾,所以 this 指向 obj。而 foo()亥啦,foo 運行在全局炭剪,所以 this 指向全局。
由此得出結(jié)論:
- 普通函數(shù)的 this 總是指向它的直接調(diào)用者翔脱。
- 在嚴格模式下奴拦,沒找到直接調(diào)用者,則函數(shù)中的 this 是 undefined届吁。
- 在默認模式下(非嚴格模式)错妖,沒找到直接調(diào)用者,則函數(shù)中的 this 指向 window疚沐。
this 的由來
JavaScript 語言之所以有 this 的設(shè)計暂氯,跟內(nèi)存里面的數(shù)據(jù)結(jié)構(gòu)有關(guān)系。
內(nèi)存數(shù)據(jù)結(jié)果
var obj = { foo: 5 };
上面的代碼將一個對象賦值給變量obj
亮蛔。Javascript 引擎會先在內(nèi)存里面校读,生成一個對象{ foo: 5 }
司倚,然后把這個對象的內(nèi)存地址賦值給變量obj
良价。
也就是說榆纽,變量obj
是一個地址(reference)。后面如果要讀取obj.foo
芬探,引擎先從obj
拿到內(nèi)存地址齿尽,然后再從該地址讀出原始的對象,返回它的foo
屬性灯节。
原始的對象以字典結(jié)構(gòu)保存,每一個屬性名都對應(yīng)一個屬性描述對象。舉例來說炎疆,上面例子的foo
屬性卡骂,實際上是以下面的形式保存的。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
注意形入,foo屬性的值保存在屬性描述對象的value屬性里面全跨。
函數(shù)
這樣的結(jié)構(gòu)是很清晰的,問題在于屬性的值可能是一個函數(shù)亿遂。
var obj = { foo: function () {} };
這時浓若,引擎會將函數(shù)單獨保存在內(nèi)存中,然后再將函數(shù)的地址賦值給foo
屬性的value
屬性蛇数。
{
foo: {
[[value]]: 函數(shù)的地址
...
}
}
由于函數(shù)是一個單獨的值挪钓,所以它可以在不同的環(huán)境(上下文)執(zhí)行。
var f = function () {};
var obj = { f: f };
// 單獨執(zhí)行
f()
// obj 環(huán)境執(zhí)行
obj.f()
環(huán)境變量
Javascript 允許在函數(shù)體內(nèi)部耳舅,引用當(dāng)前環(huán)境的其他變量碌上。
var f = function () {
console.log(x);
};
上面代碼中,函數(shù)體里面使用了變量x浦徊。該變量由運行環(huán)境提供馏予。
現(xiàn)在問題就來了,由于函數(shù)可以在不同的運行環(huán)境執(zhí)行盔性,所以需要有一種機制霞丧,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運行環(huán)境(context)。所以冕香,this
就出現(xiàn)了蛹尝,它的設(shè)計目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運行環(huán)境暂筝。
var f = function () {
console.log(this.x);
}
上面代碼中箩言,函數(shù)體里面的this.x
就是指當(dāng)前運行環(huán)境的x
。
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 單獨執(zhí)行
f() // 1
// obj 環(huán)境執(zhí)行
obj.f() // 2
上面代碼中焕襟,函數(shù)f
在全局環(huán)境執(zhí)行陨收,this.x
指向全局環(huán)境的x
。
在
obj
環(huán)境執(zhí)行鸵赖,this.x
指向obj.x
务漩。回到本文開頭提出的問題,
obj.foo()
是通過obj
找到foo
它褪,所以就是在obj
環(huán)境執(zhí)行饵骨。一旦var foo = obj.foo
,變量foo
就直接指向函數(shù)本身茫打,所以foo()
就變成在全局環(huán)境執(zhí)行居触。
箭頭函數(shù)
在 ES6 中新增的箭頭函數(shù)妖混,不僅簡化了代碼,還解決 this 飄忽不定的指向問題轮洋。
var obj = {
a : 1,
foo : function(){
setTimeout(
function(){console.log(this.a),3000})
}
}
obj.foo(); //undefined
此代碼運行結(jié)果為 undefined制市,this 的指向是全局的 window 對象。
()=>{console.log(this)}
其中()內(nèi)是要帶入的參數(shù)弊予,{}內(nèi)是要執(zhí)行的語句祥楣。箭頭函數(shù)是函數(shù)式編程的一種體現(xiàn),函數(shù)式編程將更多的關(guān)注點放在輸入和輸出的關(guān)系汉柒,省去了過程的一些因素误褪,因此箭頭函數(shù)中沒有自己的 this,arguments碾褂,new target(ES6)和 super(ES6)兽间。箭頭函數(shù)相當(dāng)于匿名函數(shù),因此不能使用new來作為構(gòu)造函數(shù)使用斋扰。
箭頭函數(shù)中的 this 始終指向其父級作用域中的 this 渡八。換句話說,箭頭函數(shù)會捕獲其所在的上下文的 this 值传货,作為自己的 this 值屎鳍。任何方法都改變不了其指向,如 call(), bind(), apply()问裕。在箭頭函數(shù)中調(diào)用 this 時逮壁,僅僅是簡單的沿著作用域鏈向上尋找,找到最近的一個 this 拿來使用粮宛,它與調(diào)用時的上下文無關(guān)窥淆。我們用代碼來解釋一下。