前言
本文翻譯自this
概述
很多程序員習(xí)慣的將this和面向?qū)ο缶o密緊密連續(xù)在一起潦嘶,this指向了構(gòu)造函數(shù)中新建的對(duì)象床佳。雖然在這個(gè)說(shuō)法在ECMAScript也是成立的怕品,但是this不僅僅是指向構(gòu)造函數(shù)的實(shí)例
定義
this
是執(zhí)行上下文的一個(gè)屬性
activeExecutionContext = {
VO: {...},
this: thisValue
};
this 與上下文的可執(zhí)行代碼類(lèi)型緊密相關(guān)峡钓,其值在進(jìn)入上下文階段就確定了槐雾,并且在執(zhí)行代碼階段不能被改變
全局代碼中的this
局代碼中的 this 非常簡(jiǎn)單夭委,this 始終是全局對(duì)象自身,因此募强,可以間接獲取引用
// 顯式定義全局對(duì)象的屬性
this.a = 10; // global.a = 10
alert(a); // 10
// 通過(guò)賦值給不受限的標(biāo)識(shí)符來(lái)進(jìn)行隱式定義
b = 20;
alert(this.b); // 20
// 通過(guò)變量聲明來(lái)進(jìn)行隱式定義
// 因?yàn)槿稚舷挛闹械淖兞繉?duì)象就是全局對(duì)象本身
var c = 30;
alert(this.c); // 30
函數(shù)代碼中的 this
this 在函數(shù)代碼中的時(shí)候株灸,事情就變得有趣多了。這種情況下是最復(fù)雜的擎值,并且會(huì)引發(fā)很多的問(wèn)題慌烧。
函數(shù)代碼中的 this的第一個(gè)(同時(shí)也是最主要)的特性就是:它并非靜態(tài)綁定在函數(shù)上。
如上所述幅恋,this 的值是進(jìn)入執(zhí)行上下文階段確定的杏死,函數(shù)代碼中的 this 的值可能每次都不一樣。
而且捆交,一旦進(jìn)入代碼執(zhí)行階段淑翼,其值就維持不變了。也就是說(shuō)品追,要給 this 賦一個(gè)新值是不可能的玄括,因?yàn)?this 根本就不是一個(gè)變量:
var foo = {x: 10};
var bar = {
x: 20,
test: function () {
alert(this === bar); // true
alert(this.x); // 20
this = foo; // error, 不能更改this的值
alert(this.x); // 如果沒(méi)有錯(cuò)誤,則其值為10而不是20
}
};
// 在進(jìn)入上下文的時(shí)候肉瓦,this 的值就確定了是“bar”對(duì)象
bar.test(); // true, 20
foo.test = bar.test;
// 但是遭京,這個(gè)時(shí)候,this的值又會(huì)變成“foo”,雖然我們調(diào)用的是同一個(gè)函數(shù)
foo.test(); // false, 10
那么泞莉,在函數(shù)代碼中有哪些因數(shù)會(huì)影響this值的變化哪雕?有如下因數(shù):
首先,在通常的函數(shù)調(diào)用時(shí)鲫趁,this是由激活上下文代碼的調(diào)用者(caller)決定的斯嚎,即調(diào)用函數(shù)的父級(jí)上下文。并且this的值是由調(diào)用表達(dá)式的形式?jīng)Q定的(換句話說(shuō)就是挨厚,由調(diào)用函數(shù)的語(yǔ)法決定)堡僻。
了解并記住這點(diǎn)非常重要,這樣才能在任何上下文中都能準(zhǔn)確判斷this的值疫剃。更確切地講钉疫,調(diào)用表達(dá)式的形式(或者說(shuō),調(diào)用函數(shù)的方式)影響了 this 的值巢价,而不是其他因素
(一些關(guān)于 JavaScript的文章和書(shū)籍中指出:“this的值取決于函數(shù)定義的方式牲阁,如果是全局函數(shù)固阁,那么this的值就是全局對(duì)象,如果函數(shù)是某個(gè)對(duì)象的方法咨油,那么this的值就是該對(duì)象” -- 這絕對(duì)不正確)您炉。下面我們將看到,即便是全局函數(shù)役电,this的值也會(huì)因?yàn)檎{(diào)用函數(shù)的方式不同而不同
function foo() {
alert(this);
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// 然而赚爵,同樣的函數(shù),以另外一種調(diào)用方式的話法瑟,this的值就不同了
foo.prototype.constructor(); // foo.prototype
同樣冀膝,調(diào)用對(duì)象中的方法,this
值也可能不是該對(duì)象
var foo = {
bar: function () {
alert(this);
alert(this === foo);
}
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true
// 同樣地霎挟,相同的函數(shù)以不同的調(diào)用方式窝剖,this的值也就不同了
exampleFunc(); // global, false
那么,究竟調(diào)用方式是如何影響this
的值酥夭?為了完全弄懂其中的奧妙赐纱,首選需要了解一種內(nèi)部類(lèi)型 - 引用(Reference)類(lèi)型
引用類(lèi)型
引用類(lèi)型可以用偽代碼表示為擁有兩個(gè)屬性的對(duì)象:base
(即擁有屬性的那個(gè)對(duì)象),和base中的 propertyName
熬北。
var valueOfReferenceType = {
base: ,
propertyName:
};
引用類(lèi)型的值只有可能是以下兩種情況:
- 當(dāng)處理一個(gè)標(biāo)識(shí)的時(shí)候
- 進(jìn)行屬性訪問(wèn)的時(shí)候
標(biāo)示符的處理過(guò)程在作用域鏈中討論;在這里我們只需要知道疙描,使用這種處理方式的返回值總是一個(gè)引用類(lèi)型的值
標(biāo)識(shí)符是變量名,函數(shù)名讶隐,函數(shù)參數(shù)名和全局對(duì)象中未識(shí)別的屬性名起胰,如下:
var foo = 10;
function bar() {}
中間過(guò)程中,對(duì)應(yīng)的引用類(lèi)型的值如下所示:
var fooReference = {
base: global,
propertyName: 'foo'
};
var barReference = {
base: global,
propertyName: 'bar'
};
要從引用類(lèi)型的值中獲取一個(gè)對(duì)象實(shí)際的值需要GetValue
方法巫延,該方法用偽代碼可以描述成如下形式:
function GetValue(value) {
if (Type(value) != Reference) {
return value;
}
var base = GetBase(value);
if (base === null) {
throw new ReferenceError;
}
return base.[[Get]](GetPropertyName(value));
}
上述代碼中的 [[Get]] 方法返回了對(duì)象屬性實(shí)際的值效五,包括從原型鏈中繼承的屬性:
GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"
對(duì)于屬性訪問(wèn)來(lái)說(shuō)洼滚,有兩種方式:點(diǎn)符號(hào)(這時(shí)屬性名是正確的標(biāo)識(shí)符并且提前已經(jīng)知道了)或者中括號(hào)符號(hào):
foo.bar();
foo['bar']();
中間過(guò)程中蝎宇,得到如下的引用類(lèi)型的值:
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
GetValue(fooBarReference); // function object "bar"
那么,引用類(lèi)型的值與函數(shù)上下文中this
的值是如何關(guān)聯(lián)起來(lái)的呢踢代?這很重要疼阔,也是本文的核心內(nèi)容瓜客。總體來(lái)說(shuō)竿开,確定函數(shù)上下文中this
值的一般規(guī)則如下:
函數(shù)上下文中this
的值由調(diào)用者(caller)提供,并由調(diào)用表達(dá)式的形式確定(函數(shù)調(diào)用的語(yǔ)法)玻熙。
如果在調(diào)用括號(hào) () 的左邊是引用類(lèi)型否彩,那么this
的值就是該引用類(lèi)型值的 base 對(duì)象。
在其他情況下(非引用類(lèi)型)嗦随,this
的值總是 null列荔。然而敬尺,null 對(duì)于 this
來(lái)說(shuō)沒(méi)有任何意義,因此為隱式轉(zhuǎn)換為全局對(duì)象
function foo() {
return this;
}
foo(); // global
上面代碼中贴浙,調(diào)用括號(hào)左側(cè)是引用類(lèi)型(因?yàn)?foo 是標(biāo)識(shí)符):
var fooReference = {
base: global,
propertyName: 'foo'
};
相應(yīng)的砂吞,this
的值會(huì)設(shè)置為引用類(lèi)型值的base對(duì)象,這里就是全局對(duì)象崎溃。
同樣蜻直,使用屬性訪問(wèn)器:
var foo = {
bar: function () {
return this;
}
};
foo.bar();
同樣,bar也是引用類(lèi)型的值袁串,它的base對(duì)象是foo對(duì)象概而,當(dāng)激活bar函數(shù)的時(shí),this
的值就設(shè)置為 foo 對(duì)象:
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
然而囱修,同樣的函數(shù)以不同的激活方式的話赎瑰,this 的值就完全不同了:
var test = foo.bar;
test(); // global
因?yàn)閠est也是標(biāo)識(shí)符,這樣就產(chǎn)生了其他引用類(lèi)型的值破镰,該值的 base(全局對(duì)象)被設(shè)置為this
的值
var testReference = {
base: global,
propertyName: 'test'
};
函數(shù)調(diào)用和非引用類(lèi)型
正如此前提到過(guò)的餐曼,當(dāng)調(diào)用括號(hào)左側(cè)為非引用類(lèi)型的時(shí)候,this
的值會(huì)設(shè)置為 null鲜漩,并最終成為全局對(duì)象
(function () {
alert(this); // null => global
})();
在這個(gè)例子中源譬,我們有一個(gè)函數(shù)對(duì)象但不是引用類(lèi)型的對(duì)象(因?yàn)樗皇菢?biāo)示符,也不是屬性訪問(wèn)器)宇整,因此 this 的值最終被設(shè)為全局對(duì)象瓶佳。
var foo = {
bar: function () {
alert(this);
}
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?
那么,為什么明明是屬性訪問(wèn)鳞青,而最終this
不是引用類(lèi)型的base對(duì)象(foo)霸饲,而是全局對(duì)象呢?
問(wèn)題出在后面三個(gè)調(diào)用臂拓,在執(zhí)行一定的操作運(yùn)算之后厚脉,在調(diào)用括號(hào)的左邊的值不再是引用類(lèi)型。
第一種情況胶惰,很明顯是引用類(lèi)型this
的值為 base 對(duì)象傻工,即 foo。
第二種情況孵滞,分組操作符沒(méi)有實(shí)際意義中捆,分組操作符返回的仍是一個(gè)引用類(lèi)型,這就是 this
的值為什么再次被設(shè)為 base 對(duì)象坊饶,即 foo泄伪。
第三種情況,賦值操作符(assignment operator)與組操作符不同匿级,它會(huì)觸發(fā)調(diào)用GetValue方法蟋滴。最后返回的時(shí)候就是一個(gè)函數(shù)對(duì)象了(而不是引用類(lèi)型的值了)染厅,這就意味著 this
的值會(huì)設(shè)置為 null,最終會(huì)變成全局對(duì)象津函。
第四和第五種情況也類(lèi)似肖粮,逗號(hào)操作符和 OR 邏輯表達(dá)式都會(huì)觸發(fā)調(diào)用GetValue
方法,于是相應(yīng)地就會(huì)丟失原先的引用類(lèi)型值尔苦,變成了函數(shù)類(lèi)型涩馆,this
的值就變成了全局對(duì)象了。
引用類(lèi)型和 this 為 null
有一種情況蕉堰,當(dāng)調(diào)用表達(dá)式左側(cè)是引用類(lèi)型的值凌净,但是 this
的值卻是null,最終變?yōu)槿謱?duì)象屋讶。發(fā)生這種情況的條件是當(dāng)引用類(lèi)型值的base對(duì)象恰好為活動(dòng)對(duì)象
function foo() {
function bar() {
alert(this); // global
}
bar(); // 和AO.bar()是一樣的
}
活躍對(duì)象總是會(huì)返回this
值為null
(用偽代碼來(lái)表示冰寻, AO.bar() 就相當(dāng)于 null.bar())。然后皿渗,如此前描述的斩芭,this
的值最終會(huì)由null
變?yōu)槿謱?duì)象
作為構(gòu)造器調(diào)用的函數(shù)中的 this
這里介紹另外一種情況,當(dāng)函數(shù)作為構(gòu)造器被調(diào)用的時(shí)候:
function A() {
alert(this); // newly created object, below - "a" object
this.x = 10;
}
var a = new A();
alert(a.x); // 10
在這種情況下乐疆,new 操作符會(huì)調(diào)用"A" 函數(shù)的內(nèi)部 [[Construct]]划乖。在對(duì)象創(chuàng)建之后,會(huì)調(diào)用內(nèi)部的 [[Call]] 函數(shù)挤土,然后所有 “A” 函數(shù)中this
的值會(huì)設(shè)置為新創(chuàng)建的對(duì)象琴庵。