本文參考鏈接 徹底理解js中this的指向 和 知乎回答 以及 JavaScript中的對象查找
下面所說的全部為標(biāo)準(zhǔn)模式下的情況,至于嚴(yán)格模式下的this捺球,請參閱另一篇文章最后一部分內(nèi)容
首先必須要說的是,this的指向在函數(shù)定義的時候是確定不了的嘶是,只有函數(shù)執(zhí)行的時候才能確定this到底指向誰行拢,實際上this的最終指向的是那個調(diào)用它的對象。
既然this最終指向調(diào)用它的對象痕囱,那么我就首先看下田轧,JS中函數(shù)的調(diào)用分那幾種情況。
JS中函數(shù)調(diào)用共分為四種類型
一:Function Invocation Pattern
諸如foo()
的調(diào)用形式被稱為Function Invocation Pattern鞍恢,是函數(shù)最直接的使用形式傻粘,注意這里的foo是作為單獨的變量出現(xiàn),而不是屬性帮掉,前面并無調(diào)用對象弦悉。
在這種模式下,foo函數(shù)體中的this永遠(yuǎn)為Global對象蟆炊,在瀏覽器中就是window對象稽莉。比如下面的代碼:
function foo(){
var user = "追夢子";
console.log(this.user); //undefined
console.log(this); //Window
}
foo();
即使在函數(shù)內(nèi)部調(diào)用,由于前面并無調(diào)用對象涩搓,this還是指向window全局對象污秆,如以下代碼
function printThis() {
console.log(this)
let print = function () { console.log(this) };
print();
}
printThis() // 第一行打印 window,第二行打印window
printThis.call([1]); // 第一行打印[1]昧甘,第二行打印window
printThis.call([2]); // 第一行打印[2]混狠,第二行打印window
在上面的代碼中,第一條將打印printThis的this疾层,當(dāng)通過call函數(shù)的時候将饺,參數(shù)作為this傳遞到函數(shù)中,因此分別打印[1]和[2]痛黎。第二條因為沒有對象調(diào)用print予弧,因此一直打印window。
即使將print改為匿名函數(shù)湖饱,比如下面的代碼掖蛤,結(jié)果依然沒有變化。因此此時匿名函數(shù)依然由window調(diào)用
function printThis() {
console.log(this)
(function () {
console.log(this)
})(); // 匿名函數(shù)和上面的print都由window調(diào)用
}
printThis() // 第一行打印 window井厌,第二行打印window
printThis.call([1]); // 第一行打印[1]蚓庭,第二行打印window
printThis.call([2]); // 第一行打印[2],第二行打印window
但是在ES6中仅仆,隨著箭頭函數(shù)的引入器赞,這種情況有所變化,將上面的函數(shù)改為下面的格式:
function printThis() {
console.log(this)
let print = () => console.log(this);
print();
}
printThis() // 第一行打印 window墓拜,第二行打印window
printThis.call([1]); // 第一行打印[1]港柜,第二行打印[1]
printThis.call([2]); // 第一行打印[2],第二行打印[2]
此時的結(jié)果和上面就有所不同,這是因為在箭頭函數(shù)中夏醉,拋棄了自己的this屬性爽锥,而是直接使用封閉執(zhí)行上下文的this值。所謂的封閉執(zhí)行上下文畔柔,就是箭頭函數(shù)出現(xiàn)的地方的代碼域氯夷。此時第一條和第二條打印內(nèi)容相同。
在箭頭函數(shù)中靶擦,所有的this原則肠槽,無論是標(biāo)準(zhǔn)模式還是嚴(yán)格模式下,都不再生效奢啥。
二:Method Invocation Pattern
諸如foo.bar()
的調(diào)用形式被稱為Method Invocation Pattern秸仙,注意其特點是被調(diào)用的函數(shù)作為一個對象的屬性出現(xiàn),必然會有“.”或者“[]”這樣的關(guān)鍵符號桩盲。
在這種模式下寂纪,bar函數(shù)體中的this永遠(yuǎn)為“.”或“[”前的那個對象,如上例中就一定是foo對象赌结。比如下面代碼:
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn();
雖然首先調(diào)用的對象為o
捞蛋,但最終的函數(shù)fn
是對象b
的一個屬性,因此this.a
為12柬姚。
然后我們再看下下面的代碼
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
var j = o.b.fn; // 只是定義拟杉,并未調(diào)用執(zhí)行,真正的調(diào)用執(zhí)行在下面的j()
j();
此時的this.a
應(yīng)該是多少呢量承?此時的this
其實為window搬设,this.a
為undefined。為什么如此呢撕捍?回顧一下拿穴,在文章的最開始,我們就提到了this的最終指向的是那個調(diào)用它的對象忧风。而我們在使用o.b.fn
的時候默色,并沒有調(diào)用執(zhí)行,真正的調(diào)用執(zhí)行是在下面的j()狮腿,此時的情況和第一種方式一樣腿宰。
三:Constructor Pattern
new foo()
這種形式的調(diào)用被稱為Constructor Pattern,其關(guān)鍵字new
就很能說明問題缘厢,非常容易識別吃度。
在這種模式下,foo函數(shù)內(nèi)部的this永遠(yuǎn)是new foo()返回的對象昧绣。比如下面的代碼:
function Foo () {
this.x = 1;
}
Foo.prototype.print = function () {
console.log(this.x);
}
let foo = new Foo();
foo.print();
foo.print.call({x: 2});
此時规肴,第一條打印為1捶闸,表示this為foo
實例對象夜畴。this.x
為constructor函數(shù)中賦值的1拖刃。第二條打印為2,因此為使用了{x: 2}
作為對象替換了foo
實例對象中的this
贪绘。
在這里需要注意Function創(chuàng)建對象的一種特殊情況
function Foo () {
this.x = 1;
return {x: 2};
}
Foo.prototype.print = function () {
console.log(this.x);
}
let foo = new Foo();
console.log(foo.x); // 2
foo.print(); // funciton print undefined
如果構(gòu)建函數(shù)Foo返回的是一個對象兑牡,那么foo就會被這個對象所替換掉,此時foo為{x: 2}
税灌,其中只有一個x屬性等于2均函,并無print這個方法屬性。但是如果我們修改下代碼菱涤,改成下面的格式:
function Foo () {
this.x = 1;
return 1;
}
Foo.prototype.print = function () {
console.log(this.x);
}
let foo = new Foo();
console.log(foo.x); // 1
foo.print(); // 1
因為構(gòu)建函數(shù)Foo返回的是并不是一個對象苞也,那么foo就不會被替換掉,依然是Foo的一個實例對象粘秆,此時的foo.x
和this.x
全部為1如迟。
四:Apply Pattern
foo.call(thisObject)
和foo.apply(thisObject)
的形式被稱為Apply Pattern,使用了內(nèi)置的call
和apply
函數(shù)攻走。
在這種模式下殷勘,call
和apply
的第一個參數(shù)就是foo函數(shù)體內(nèi)的this,如果thisObject是null
或undefined
昔搂,那么會變成window對象玲销。具體代碼,我們在上面的三種模式中已經(jīng)順帶闡述摘符,因此不再贅述贤斜。
練習(xí)部分
查看下下面的代碼,分析下打印的結(jié)果逛裤,然后實際運(yùn)行下蠢古,看下是否和結(jié)果一致
var x = 0;
function Foo () {
this.x = 1;
}
Foo.prototype.print = function () {
console.log(this);
console.log(this.x);
(function () {
console.log(this);
console.log(this.x)
})()
}
let foo = new Foo();
foo.print.call({x: 2});
查看代碼,首先注意到Foo對象的print中别凹,第三條和第四條打印是在一個匿名函數(shù)中草讶,此時該匿名函數(shù)的調(diào)用者為window全局變量,因此第四條打印中的this.x
為window.x = 0
炉菲。再往下看堕战,發(fā)現(xiàn)使用{x:2}
代替了print函數(shù)中的this
,因此第一條打印為{x: 2}
拍霜,第二條打印為2嘱丢。
如果修改下代碼,將print的定義改為箭頭函數(shù)呢祠饺?結(jié)果又如何越驻?代碼如下:
var x = 0;
function Foo () {
this.x = 1;
}
Foo.prototype.print = () => {
console.log(this);
console.log(this.x);
(function () {
console.log(this);
console.log(this.x)
})()
}
let foo = new Foo();
foo.print.call({x: 2});
首先,我們知道箭頭函數(shù)不包含this,它的this為執(zhí)行上下文中的this缀旁。而且箭頭函數(shù)的執(zhí)行上下文的判定记劈,就在其定義的時刻決定,我們發(fā)現(xiàn)并巍,它是在腳本中定義的目木,此時的作用域為全局,因此此時懊渡,前兩條打印和后兩條打印一樣刽射,都為0和window。