JavaScript ECMA-262-3 深入解析.第三章.this
更新時(shí)間:2011年09月28日 00:44:40 作者:
在這篇文章里,我們將討論跟執(zhí)行上下文直接相關(guān)的更多細(xì)節(jié)。討論的主題就是this關(guān)鍵字
介紹
在這篇文章里,我們將討論跟執(zhí)行上下文直接相關(guān)的更多細(xì)節(jié)。討論的主題就是this關(guān)鍵字。
實(shí)踐證明,這個(gè)主題很難,在不同執(zhí)行上下文中確定this的值經(jīng)常會(huì)發(fā)生問題。
許多程序員習(xí)慣的認(rèn)為,在程序語言中,this關(guān)鍵字與面向?qū)ο蟪绦蜷_發(fā)緊密相關(guān),其完全指向由構(gòu)造器新創(chuàng)建的對象。在ECMAScript規(guī)范中也是這樣實(shí)現(xiàn)的,但正如我們將看到那樣,在ECMAScript中,this并不限于只用來指向新創(chuàng)建的對象。
下面讓我們更詳細(xì)的了解一下,在ECMAScript中this的值到底是什么?
定義
this是執(zhí)行上下文中的一個(gè)屬性:
activeExecutionContext = {
VO: {...},
this: thisValue
};
這里VO是我們前一章討論的變量對象。
this與上下文中可執(zhí)行代碼(的類型)直接相關(guān)。this的值在進(jìn)入上下文時(shí)確定,并且在上下文運(yùn)行代碼期間不會(huì)改變this的值。
下面讓我們更詳細(xì)研究這些場景。
this在全局代碼中的值
在這里一切都很簡單。在全局代碼中,this始終是全局對象本身,這樣就有可能間接的引用到它了。
// explicit property definition of
// the global object
this.a = 10; // global.a = 10
alert(a); // 10
// implicit definition via assigning
// to unqualified identifier
b = 20;
alert(this.b); // 20
// also implicit via variable declaration
// because variable object of the global context
// is the global object itself
var c = 30;
alert(this.c); // 30
this在函數(shù)代碼中的值
在函數(shù)代碼中使用this時(shí)很有趣,這種應(yīng)用場景很難且會(huì)導(dǎo)致很多問題。
在這種類型的代碼中,this值的首要(也許是最主要的)特點(diǎn)是它沒有靜態(tài)綁定到一個(gè)函數(shù)。
正如我們上面曾提到的那樣,this的值在進(jìn)入上下文時(shí)確定,在函數(shù)代碼中,this的值每一次(進(jìn)入上下文時(shí))可能完全不同。
不管怎樣,在代碼運(yùn)行期間,this的值是不變的,也就是說,因?yàn)閠his不是一個(gè)變量,所以不可能為其分配一個(gè)新值。(相反,在Python編程語言中,它明確的定義為對象本身,在運(yùn)行期間可以不斷改變)。
var foo = {x: 10};
var bar = {
x: 20,
test: function () {
alert(this === bar); // true
alert(this.x); // 20
this = foo; // error
alert(this.x); // if there wasn't an error then 20, not 10
}
};
// on entering the context this value is
// determined as "bar" object; why so - will
// be discussed below in detail
bar.test(); // true, 20
foo.test = bar.test;
// however here this value will now refer
// to "foo" – even though we're calling the same function
foo.test(); // false, 10
那么,在函數(shù)代碼中,什么影響了this的值發(fā)生變化?有幾個(gè)因素。
首先,在通常的函數(shù)調(diào)用中,this是由激活上下文代碼的調(diào)用者來提供的,即調(diào)用函數(shù)的父上下文(parent context)。this取決于調(diào)用函數(shù)的方式。(譯者注:參考這里)
為了在任何情況下準(zhǔn)確無誤的確定this值,有必要理解和記住這重要的一點(diǎn):正是調(diào)用函數(shù)的方式影響了調(diào)用的上下文中this的值,沒有別的什么(我們可以在一些文章,甚至是在關(guān)于javascript的書籍中看到,它們聲稱:“this的值取決于函數(shù)如何定義,如果它是全局函數(shù),this設(shè)置為全局對象,如果函數(shù)是一個(gè)對象的方法,this將總是指向這個(gè)對象。–這絕對不正確”)。繼續(xù)我們的話題,可以看到,即使是正常的全局函數(shù)也會(huì)因?yàn)椴煌{(diào)用方式而激活,這些不同調(diào)用方式產(chǎn)生了this不同的值。
function foo() {
alert(this);
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// but with another form of the call expression
// of the same function, this value is different
foo.prototype.constructor(); // foo.prototype
有時(shí)可能將函數(shù)作為某些對象的一個(gè)方法來調(diào)用,此時(shí)this的值不會(huì)設(shè)置為這個(gè)對象。
var foo = {
bar: function () {
alert(this);
alert(this === foo);
}
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true
// again with another form of the call expression
// of the same function, we have different this value
exampleFunc(); // global, false
那么,到底調(diào)用函數(shù)的方式如何影響this的值?為了充分理解this的值是如何確定的,我們需要詳細(xì)分析一個(gè)內(nèi)部類型(internal type)——引用類型(Reference type)。
引用類型
用偽代碼可以把引用類型表示為擁有兩個(gè)屬性的對象——base(即擁有屬性的那個(gè)對象),和base中的propertyName 。
var valueOfReferenceType = {
base: <base object>,
propertyName: <property name>
};
引用類型的值僅存在于兩種情況中:
1. 當(dāng)我們處理一個(gè)標(biāo)示符時(shí);(when we deal with an identifier;)
2. 或一個(gè)屬性訪問器;(or with a property accessor.)
標(biāo)示符的處理過程在 Chapter 4. Scope chain中討論;在這里我們只需要知道,使用這種處理方式的返回值總是一個(gè)引用類型的值(這對this來說很重要)。
標(biāo)識(shí)符是變量名,函數(shù)名,函數(shù)參數(shù)名和全局對象中未識(shí)別的屬性名。例如,下面標(biāo)識(shí)符的值:
var foo = 10;
function bar() {}
在操作的中間結(jié)果中,引用類型對應(yīng)的值如下:
var fooReference = {
base: global,
propertyName: 'foo'
};
var barReference = {
base: global,
propertyName: 'bar'
};
為了從引用類型中得到一個(gè)對象真正的值,在偽代碼中可以用GetValue方法(譯者注:11.1.6)來表示,如下:
function GetValue(value) {
if (Type(value) != Reference) {
return value;
}
var base = GetBase(value);
if (base === null) {
throw new ReferenceError;
}
return base.[[Get]](GetPropertyName(value));
}
內(nèi)部的[[Get]]方法返回對象屬性真正的值,包括對原型鏈中繼承屬性的分析。
GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"
屬性訪問器都應(yīng)該熟悉。它有兩種變體:點(diǎn)(.)語法(此時(shí)屬性名是正確的標(biāo)示符,且事先知道),或括號語法([])。
foo.bar();
foo['bar']();
在計(jì)算中間的返回值中,引用類型對應(yīng)的值如下:
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
GetValue(fooBarReference); // function object "bar"
那么,從最重要的意義上來說,引用類型的值與函數(shù)上下文中的this的值是如何關(guān)聯(lián)起來的呢?這個(gè)關(guān)聯(lián)的過程是這篇文章的核心。(The given moment is the main of this article.) 在一個(gè)函數(shù)上下文中確定this的值的通用規(guī)則如下:
在一個(gè)函數(shù)上下文中,this的值由調(diào)用者提供,且由調(diào)用函數(shù)的方式?jīng)Q定。如果調(diào)用括號()的左邊是引用類型的值,this將設(shè)為這個(gè)引用類型值的base對象,在其他情況下(與引用類型不同的任何其它屬性),this的值都為null。不過,實(shí)際不存在this的值為null的情況,因?yàn)楫?dāng)this的值為null的時(shí)候,其值會(huì)被隱式轉(zhuǎn)換為全局對象。
下面讓我們看個(gè)例子:
function foo() {
return this;
}
foo(); // global
我們看到在調(diào)用括號的左邊是一個(gè)引用類型值(因?yàn)閒oo是一個(gè)標(biāo)示符):
var fooReference = {
base: global,
propertyName: 'foo'
};
相應(yīng)地,this也設(shè)置為引用類型的base對象。即全局對象。
同樣,使用屬性訪問器:
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
同樣,我們擁有一個(gè)引用類型的值,其base是foo對象,在函數(shù)bar激活時(shí)將base設(shè)置給this。
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
但是,如果用另一種方式激活相同的函數(shù),this的值將不同。
var test = foo.bar;
test(); // global
因?yàn)閠est作為標(biāo)識(shí)符,產(chǎn)生了其他引用類型的值,該值的base(全局對象)被設(shè)置為this的值。
var testReference = {
base: global,
propertyName: 'test'
};
現(xiàn)在,我們可以很明確的說明,為什么用不同的形式激活同一個(gè)函數(shù)會(huì)產(chǎn)生不同的this,答案在于不同的引用類型(type Reference)的中間值。
function foo() {
alert(this);
}
foo(); // global, because
var fooReference = {
base: global,
propertyName: 'foo'
};
alert(foo === foo.prototype.constructor); // true
// another form of the call expression
foo.prototype.constructor(); // foo.prototype, because
var fooPrototypeConstructorReference = {
base: foo.prototype,
propertyName: 'constructor'
};
另一個(gè)通過調(diào)用方式動(dòng)態(tài)確定this的值的經(jīng)典例子:
function foo() {
alert(this.bar);
}
var x = {bar: 10};
var y = {bar: 20};
x.test = foo;
y.test = foo;
x.test(); // 10
y.test(); // 20
函數(shù)調(diào)用和非引用類型
那么,正如我們已經(jīng)指出,當(dāng)調(diào)用括號的左邊不是引用類型而是其它類型,this的值自動(dòng)設(shè)置為null,實(shí)際最終this的值被隱式轉(zhuǎn)換為全局對象。
讓我們思考下面這種函數(shù)表達(dá)式:
(function () {
alert(this); // null => global
})();
在這個(gè)例子中,我們有一個(gè)函數(shù)對象但不是引用類型的對象(因?yàn)樗皇菢?biāo)示符,也不是屬性訪問器),相應(yīng)地,this的值最終被設(shè)為全局對象。
更多復(fù)雜的例子:
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?
那么,為什么我們有一個(gè)屬性訪問器,它的中間值應(yīng)該為引用類型的值,但是在某些調(diào)用中我們得到this的值不是base對象,而是global對象?
問題出現(xiàn)在后面的三個(gè)調(diào)用,在執(zhí)行一定的操作運(yùn)算之后,在調(diào)用括號的左邊的值不再是引用類型。
第一個(gè)例子很明顯———明顯的引用類型,結(jié)果是,this為base對象,即foo。
在第二個(gè)例子中,分組操作符(譯者注:這里的分組操作符就是指foo.bar外面的括號"()")沒有實(shí)際意義,想想上面提到的,從引用類型中獲得一個(gè)對象真正的值的方法,如GetValue (參考11.1.6)。相應(yīng)的,在分組操作的返回值中———我們得到的仍是一個(gè)引用類型。這就是this的值為什么再次被設(shè)為base對象,即 foo。
第三個(gè)例子中,與分組操作符不同,賦值操作符調(diào)用了GetValue方法(參考11.13.1的第三步)。返回的結(jié)果已經(jīng)是函數(shù)對象(不是引用類型),這意味著this的值被設(shè)為null,實(shí)際最終結(jié)果是被設(shè)置為global對象。
第四個(gè)和第五個(gè)也是一樣——逗號操作符和邏輯操作符(OR)調(diào)用了GetValue 方法,相應(yīng)地,我們失去了引用類型的值而得到了函數(shù)類型的值,所以this的值再次被設(shè)為global對象。
引用類型和this為null
有一種情況,如果調(diào)用方式確定了引用類型的值(when call expression determinates on the left hand side of call brackets the value of Reference type。譯者注,原文有點(diǎn)拖沓!),不管怎樣,只要this的值被設(shè)置為null,其最終就會(huì)被隱式轉(zhuǎn)換成global。當(dāng)引用類型值的base對象是激活對象時(shí),就會(huì)導(dǎo)致這種情況。
下面的實(shí)例中,內(nèi)部函數(shù)被父函數(shù)調(diào)用,此時(shí)我們就能夠看到上面說的那種特殊情況。正如我們在 第二章 學(xué)到的一樣,局部變量、內(nèi)部函數(shù)、形式參數(shù)都儲(chǔ)存在給定函數(shù)的激活對象中。
function foo() {
function bar() {
alert(this); // global
}
bar(); // the same as AO.bar()
}
激活對象總是作為this的值返回——null(即偽代碼AO.bar()相當(dāng)于null.bar())。(譯者注:不明白參考這里)這里我們再次回到上面描述的情況,this的值最終還是被設(shè)置為全局對象。
有一種情況除外:“在with語句中調(diào)用函數(shù),且在with對象(譯者注:即下面例子中的__withObject)中包含函數(shù)名屬性時(shí)”。with語句將其對象添加在作用域鏈最前端,即在激活對象的前面。那么對應(yīng)的,引用類型有值(通過標(biāo)識(shí)符或?qū)傩栽L問器),其base對象不再是激活對象,而是with語句的對象。順便提一句,這種情況不僅跟內(nèi)部函數(shù)相關(guān),還跟全局函數(shù)相關(guān),因?yàn)閣ith對象比作用域鏈里的最前端的對象(全局對象或一個(gè)激活對象)還要靠前。
var x = 10;
with ({
foo: function () {
alert(this.x);
},
x: 20
}) {
foo(); // 20
}
// because
var fooReference = {
base: __withObject,
propertyName: 'foo'
};
在catch語句的實(shí)際參數(shù)中的函數(shù)調(diào)用存在類似情況:在這種情況下,catch對象被添加到作用域的最前端,即在激活對象或全局對象的前面。但是,這個(gè)特定的行為被確認(rèn)為是ECMA-262-3的一個(gè)bug,這個(gè)在新版的ECMA-262-5中修復(fù)了。修復(fù)后,在特定的激活對象中,this指向全局對象。而不是catch對象。
try {
throw function () {
alert(this);
};
} catch (e) {
e(); // __catchObject - in ES3, global - fixed in ES5
}
// on idea
var eReference = {
base: __catchObject,
propertyName: 'e'
};
// but, as this is a bug
// then this value is forced to global
// null => global
var eReference = {
base: global,
propertyName: 'e'
};
同樣的情況出現(xiàn)在命名函數(shù)(函數(shù)的更多細(xì)節(jié)參考Chapter 5. Functions)的遞歸調(diào)用中。在函數(shù)的第一次調(diào)用中,base對象是父激活對象(或全局對象),在遞歸調(diào)用中,base對象應(yīng)該是存儲(chǔ)著函數(shù)表達(dá)式可選名稱的特定對象。但是,在這種情況下,this的值也總是被設(shè)置為global。
(function foo(bar) {
alert(this);
!bar && foo(1); // "should" be special object, but always (correct) global
})(); // global
this在作為構(gòu)造器調(diào)用的函數(shù)中的值
還有一個(gè)在函數(shù)的上下文中與this的值相關(guān)的情況是:函數(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
在這個(gè)例子中,new操作符調(diào)用“A”函數(shù)內(nèi)部的[[Construct]]方法,接著,在對象創(chuàng)建后,調(diào)用其內(nèi)部的[[Call]]方法,所有相同的函數(shù)“A”都將this的值設(shè)置為新創(chuàng)建的對象。
手動(dòng)設(shè)置一個(gè)函數(shù)調(diào)用的this
在Function.prototype中定義了兩個(gè)方法允許手動(dòng)設(shè)置函數(shù)調(diào)用時(shí)this的值,它們是.apply和.call方法(所有的函數(shù)都可以訪問它們)。它們用接受的第一個(gè)參數(shù)作為this的值,this在調(diào)用的作用域中使用。這兩個(gè)方法的區(qū)別不大,對于.apply,第二個(gè)參數(shù)必須是數(shù)組(或者是類似數(shù)組的對象,如arguments,相反,.call能接受任何參數(shù)。兩個(gè)方法必須的參數(shù)都是第一個(gè)——this。
例如
var b = 10;
function a(c) {
alert(this.b);
alert(c);
}
a(20); // this === global, this.b == 10, c == 20
a.call({b: 20}, 30); // this === {b: 20}, this.b == 20, c == 30
a.apply({b: 30}, [40]) // this === {b: 30}, this.b == 30, c == 40
結(jié)論
在這篇文章中,我們討論了ECMAScript中this關(guān)鍵字的特征(and they really are features, in contrast, say, with C++ or Java,譯者注:這句話沒什么大用,還不知道咋翻好,暫不翻了)。我希望這篇文章有助于你準(zhǔn)確的理解ECMAScript中this關(guān)鍵字如何工作。同樣,我很高興在評論中回答您的問題。
其他參考
10.1.7 – This;
11.1.1 – The this keyword;
11.2.2 – The new operator;
11.2.3 – Function calls.
英文地址 : ECMA-262-3 in detail. Chapter 3. This.
中文地址 : [JavaScript]ECMA-262-3 深入解析.第三章.this
翻譯聲明:
1.因?yàn)镈enis已經(jīng)翻譯過這篇文章,所以該篇譯文在部分章節(jié)參考了他的譯文,參考引用部分大概占整篇文章的30%左右,另外70%左右完全是重新翻譯的。
2.在翻譯過程中,跟原作者進(jìn)行了充分的溝通,大家看譯文的時(shí)候,可以多參考原文的留言列表。
3.再好的翻譯也趕不上原汁原味的原文,所以推薦大家看過譯文之后還是要再仔細(xì)看看原文。
在這篇文章里,我們將討論跟執(zhí)行上下文直接相關(guān)的更多細(xì)節(jié)。討論的主題就是this關(guān)鍵字。
實(shí)踐證明,這個(gè)主題很難,在不同執(zhí)行上下文中確定this的值經(jīng)常會(huì)發(fā)生問題。
許多程序員習(xí)慣的認(rèn)為,在程序語言中,this關(guān)鍵字與面向?qū)ο蟪绦蜷_發(fā)緊密相關(guān),其完全指向由構(gòu)造器新創(chuàng)建的對象。在ECMAScript規(guī)范中也是這樣實(shí)現(xiàn)的,但正如我們將看到那樣,在ECMAScript中,this并不限于只用來指向新創(chuàng)建的對象。
下面讓我們更詳細(xì)的了解一下,在ECMAScript中this的值到底是什么?
定義
this是執(zhí)行上下文中的一個(gè)屬性:
復(fù)制代碼 代碼如下:
activeExecutionContext = {
VO: {...},
this: thisValue
};
這里VO是我們前一章討論的變量對象。
this與上下文中可執(zhí)行代碼(的類型)直接相關(guān)。this的值在進(jìn)入上下文時(shí)確定,并且在上下文運(yùn)行代碼期間不會(huì)改變this的值。
下面讓我們更詳細(xì)研究這些場景。
this在全局代碼中的值
在這里一切都很簡單。在全局代碼中,this始終是全局對象本身,這樣就有可能間接的引用到它了。
復(fù)制代碼 代碼如下:
// explicit property definition of
// the global object
this.a = 10; // global.a = 10
alert(a); // 10
// implicit definition via assigning
// to unqualified identifier
b = 20;
alert(this.b); // 20
// also implicit via variable declaration
// because variable object of the global context
// is the global object itself
var c = 30;
alert(this.c); // 30
this在函數(shù)代碼中的值
在函數(shù)代碼中使用this時(shí)很有趣,這種應(yīng)用場景很難且會(huì)導(dǎo)致很多問題。
在這種類型的代碼中,this值的首要(也許是最主要的)特點(diǎn)是它沒有靜態(tài)綁定到一個(gè)函數(shù)。
正如我們上面曾提到的那樣,this的值在進(jìn)入上下文時(shí)確定,在函數(shù)代碼中,this的值每一次(進(jìn)入上下文時(shí))可能完全不同。
不管怎樣,在代碼運(yùn)行期間,this的值是不變的,也就是說,因?yàn)閠his不是一個(gè)變量,所以不可能為其分配一個(gè)新值。(相反,在Python編程語言中,它明確的定義為對象本身,在運(yùn)行期間可以不斷改變)。
復(fù)制代碼 代碼如下:
var foo = {x: 10};
var bar = {
x: 20,
test: function () {
alert(this === bar); // true
alert(this.x); // 20
this = foo; // error
alert(this.x); // if there wasn't an error then 20, not 10
}
};
// on entering the context this value is
// determined as "bar" object; why so - will
// be discussed below in detail
bar.test(); // true, 20
foo.test = bar.test;
// however here this value will now refer
// to "foo" – even though we're calling the same function
foo.test(); // false, 10
那么,在函數(shù)代碼中,什么影響了this的值發(fā)生變化?有幾個(gè)因素。
首先,在通常的函數(shù)調(diào)用中,this是由激活上下文代碼的調(diào)用者來提供的,即調(diào)用函數(shù)的父上下文(parent context)。this取決于調(diào)用函數(shù)的方式。(譯者注:參考這里)
為了在任何情況下準(zhǔn)確無誤的確定this值,有必要理解和記住這重要的一點(diǎn):正是調(diào)用函數(shù)的方式影響了調(diào)用的上下文中this的值,沒有別的什么(我們可以在一些文章,甚至是在關(guān)于javascript的書籍中看到,它們聲稱:“this的值取決于函數(shù)如何定義,如果它是全局函數(shù),this設(shè)置為全局對象,如果函數(shù)是一個(gè)對象的方法,this將總是指向這個(gè)對象。–這絕對不正確”)。繼續(xù)我們的話題,可以看到,即使是正常的全局函數(shù)也會(huì)因?yàn)椴煌{(diào)用方式而激活,這些不同調(diào)用方式產(chǎn)生了this不同的值。
復(fù)制代碼 代碼如下:
function foo() {
alert(this);
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// but with another form of the call expression
// of the same function, this value is different
foo.prototype.constructor(); // foo.prototype
有時(shí)可能將函數(shù)作為某些對象的一個(gè)方法來調(diào)用,此時(shí)this的值不會(huì)設(shè)置為這個(gè)對象。
復(fù)制代碼 代碼如下:
var foo = {
bar: function () {
alert(this);
alert(this === foo);
}
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true
// again with another form of the call expression
// of the same function, we have different this value
exampleFunc(); // global, false
那么,到底調(diào)用函數(shù)的方式如何影響this的值?為了充分理解this的值是如何確定的,我們需要詳細(xì)分析一個(gè)內(nèi)部類型(internal type)——引用類型(Reference type)。
引用類型
用偽代碼可以把引用類型表示為擁有兩個(gè)屬性的對象——base(即擁有屬性的那個(gè)對象),和base中的propertyName 。
復(fù)制代碼 代碼如下:
var valueOfReferenceType = {
base: <base object>,
propertyName: <property name>
};
引用類型的值僅存在于兩種情況中:
1. 當(dāng)我們處理一個(gè)標(biāo)示符時(shí);(when we deal with an identifier;)
2. 或一個(gè)屬性訪問器;(or with a property accessor.)
標(biāo)示符的處理過程在 Chapter 4. Scope chain中討論;在這里我們只需要知道,使用這種處理方式的返回值總是一個(gè)引用類型的值(這對this來說很重要)。
標(biāo)識(shí)符是變量名,函數(shù)名,函數(shù)參數(shù)名和全局對象中未識(shí)別的屬性名。例如,下面標(biāo)識(shí)符的值:
var foo = 10;
function bar() {}
在操作的中間結(jié)果中,引用類型對應(yīng)的值如下:
復(fù)制代碼 代碼如下:
var fooReference = {
base: global,
propertyName: 'foo'
};
var barReference = {
base: global,
propertyName: 'bar'
};
為了從引用類型中得到一個(gè)對象真正的值,在偽代碼中可以用GetValue方法(譯者注:11.1.6)來表示,如下:
復(fù)制代碼 代碼如下:
function GetValue(value) {
if (Type(value) != Reference) {
return value;
}
var base = GetBase(value);
if (base === null) {
throw new ReferenceError;
}
return base.[[Get]](GetPropertyName(value));
}
內(nèi)部的[[Get]]方法返回對象屬性真正的值,包括對原型鏈中繼承屬性的分析。
GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"
屬性訪問器都應(yīng)該熟悉。它有兩種變體:點(diǎn)(.)語法(此時(shí)屬性名是正確的標(biāo)示符,且事先知道),或括號語法([])。
foo.bar();
foo['bar']();
在計(jì)算中間的返回值中,引用類型對應(yīng)的值如下:
復(fù)制代碼 代碼如下:
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
GetValue(fooBarReference); // function object "bar"
那么,從最重要的意義上來說,引用類型的值與函數(shù)上下文中的this的值是如何關(guān)聯(lián)起來的呢?這個(gè)關(guān)聯(lián)的過程是這篇文章的核心。(The given moment is the main of this article.) 在一個(gè)函數(shù)上下文中確定this的值的通用規(guī)則如下:
在一個(gè)函數(shù)上下文中,this的值由調(diào)用者提供,且由調(diào)用函數(shù)的方式?jīng)Q定。如果調(diào)用括號()的左邊是引用類型的值,this將設(shè)為這個(gè)引用類型值的base對象,在其他情況下(與引用類型不同的任何其它屬性),this的值都為null。不過,實(shí)際不存在this的值為null的情況,因?yàn)楫?dāng)this的值為null的時(shí)候,其值會(huì)被隱式轉(zhuǎn)換為全局對象。
下面讓我們看個(gè)例子:
復(fù)制代碼 代碼如下:
function foo() {
return this;
}
foo(); // global
我們看到在調(diào)用括號的左邊是一個(gè)引用類型值(因?yàn)閒oo是一個(gè)標(biāo)示符):
復(fù)制代碼 代碼如下:
var fooReference = {
base: global,
propertyName: 'foo'
};
相應(yīng)地,this也設(shè)置為引用類型的base對象。即全局對象。
同樣,使用屬性訪問器:
復(fù)制代碼 代碼如下:
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
同樣,我們擁有一個(gè)引用類型的值,其base是foo對象,在函數(shù)bar激活時(shí)將base設(shè)置給this。
復(fù)制代碼 代碼如下:
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
但是,如果用另一種方式激活相同的函數(shù),this的值將不同。
var test = foo.bar;
test(); // global
因?yàn)閠est作為標(biāo)識(shí)符,產(chǎn)生了其他引用類型的值,該值的base(全局對象)被設(shè)置為this的值。
復(fù)制代碼 代碼如下:
var testReference = {
base: global,
propertyName: 'test'
};
現(xiàn)在,我們可以很明確的說明,為什么用不同的形式激活同一個(gè)函數(shù)會(huì)產(chǎn)生不同的this,答案在于不同的引用類型(type Reference)的中間值。
復(fù)制代碼 代碼如下:
function foo() {
alert(this);
}
foo(); // global, because
var fooReference = {
base: global,
propertyName: 'foo'
};
alert(foo === foo.prototype.constructor); // true
// another form of the call expression
foo.prototype.constructor(); // foo.prototype, because
var fooPrototypeConstructorReference = {
base: foo.prototype,
propertyName: 'constructor'
};
另一個(gè)通過調(diào)用方式動(dòng)態(tài)確定this的值的經(jīng)典例子:
復(fù)制代碼 代碼如下:
function foo() {
alert(this.bar);
}
var x = {bar: 10};
var y = {bar: 20};
x.test = foo;
y.test = foo;
x.test(); // 10
y.test(); // 20
函數(shù)調(diào)用和非引用類型
那么,正如我們已經(jīng)指出,當(dāng)調(diào)用括號的左邊不是引用類型而是其它類型,this的值自動(dòng)設(shè)置為null,實(shí)際最終this的值被隱式轉(zhuǎn)換為全局對象。
讓我們思考下面這種函數(shù)表達(dá)式:
復(fù)制代碼 代碼如下:
(function () {
alert(this); // null => global
})();
在這個(gè)例子中,我們有一個(gè)函數(shù)對象但不是引用類型的對象(因?yàn)樗皇菢?biāo)示符,也不是屬性訪問器),相應(yīng)地,this的值最終被設(shè)為全局對象。
更多復(fù)雜的例子:
復(fù)制代碼 代碼如下:
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?
那么,為什么我們有一個(gè)屬性訪問器,它的中間值應(yīng)該為引用類型的值,但是在某些調(diào)用中我們得到this的值不是base對象,而是global對象?
問題出現(xiàn)在后面的三個(gè)調(diào)用,在執(zhí)行一定的操作運(yùn)算之后,在調(diào)用括號的左邊的值不再是引用類型。
第一個(gè)例子很明顯———明顯的引用類型,結(jié)果是,this為base對象,即foo。
在第二個(gè)例子中,分組操作符(譯者注:這里的分組操作符就是指foo.bar外面的括號"()")沒有實(shí)際意義,想想上面提到的,從引用類型中獲得一個(gè)對象真正的值的方法,如GetValue (參考11.1.6)。相應(yīng)的,在分組操作的返回值中———我們得到的仍是一個(gè)引用類型。這就是this的值為什么再次被設(shè)為base對象,即 foo。
第三個(gè)例子中,與分組操作符不同,賦值操作符調(diào)用了GetValue方法(參考11.13.1的第三步)。返回的結(jié)果已經(jīng)是函數(shù)對象(不是引用類型),這意味著this的值被設(shè)為null,實(shí)際最終結(jié)果是被設(shè)置為global對象。
第四個(gè)和第五個(gè)也是一樣——逗號操作符和邏輯操作符(OR)調(diào)用了GetValue 方法,相應(yīng)地,我們失去了引用類型的值而得到了函數(shù)類型的值,所以this的值再次被設(shè)為global對象。
引用類型和this為null
有一種情況,如果調(diào)用方式確定了引用類型的值(when call expression determinates on the left hand side of call brackets the value of Reference type。譯者注,原文有點(diǎn)拖沓!),不管怎樣,只要this的值被設(shè)置為null,其最終就會(huì)被隱式轉(zhuǎn)換成global。當(dāng)引用類型值的base對象是激活對象時(shí),就會(huì)導(dǎo)致這種情況。
下面的實(shí)例中,內(nèi)部函數(shù)被父函數(shù)調(diào)用,此時(shí)我們就能夠看到上面說的那種特殊情況。正如我們在 第二章 學(xué)到的一樣,局部變量、內(nèi)部函數(shù)、形式參數(shù)都儲(chǔ)存在給定函數(shù)的激活對象中。
復(fù)制代碼 代碼如下:
function foo() {
function bar() {
alert(this); // global
}
bar(); // the same as AO.bar()
}
激活對象總是作為this的值返回——null(即偽代碼AO.bar()相當(dāng)于null.bar())。(譯者注:不明白參考這里)這里我們再次回到上面描述的情況,this的值最終還是被設(shè)置為全局對象。
有一種情況除外:“在with語句中調(diào)用函數(shù),且在with對象(譯者注:即下面例子中的__withObject)中包含函數(shù)名屬性時(shí)”。with語句將其對象添加在作用域鏈最前端,即在激活對象的前面。那么對應(yīng)的,引用類型有值(通過標(biāo)識(shí)符或?qū)傩栽L問器),其base對象不再是激活對象,而是with語句的對象。順便提一句,這種情況不僅跟內(nèi)部函數(shù)相關(guān),還跟全局函數(shù)相關(guān),因?yàn)閣ith對象比作用域鏈里的最前端的對象(全局對象或一個(gè)激活對象)還要靠前。
復(fù)制代碼 代碼如下:
var x = 10;
with ({
foo: function () {
alert(this.x);
},
x: 20
}) {
foo(); // 20
}
// because
var fooReference = {
base: __withObject,
propertyName: 'foo'
};
在catch語句的實(shí)際參數(shù)中的函數(shù)調(diào)用存在類似情況:在這種情況下,catch對象被添加到作用域的最前端,即在激活對象或全局對象的前面。但是,這個(gè)特定的行為被確認(rèn)為是ECMA-262-3的一個(gè)bug,這個(gè)在新版的ECMA-262-5中修復(fù)了。修復(fù)后,在特定的激活對象中,this指向全局對象。而不是catch對象。
復(fù)制代碼 代碼如下:
try {
throw function () {
alert(this);
};
} catch (e) {
e(); // __catchObject - in ES3, global - fixed in ES5
}
// on idea
var eReference = {
base: __catchObject,
propertyName: 'e'
};
// but, as this is a bug
// then this value is forced to global
// null => global
var eReference = {
base: global,
propertyName: 'e'
};
同樣的情況出現(xiàn)在命名函數(shù)(函數(shù)的更多細(xì)節(jié)參考Chapter 5. Functions)的遞歸調(diào)用中。在函數(shù)的第一次調(diào)用中,base對象是父激活對象(或全局對象),在遞歸調(diào)用中,base對象應(yīng)該是存儲(chǔ)著函數(shù)表達(dá)式可選名稱的特定對象。但是,在這種情況下,this的值也總是被設(shè)置為global。
復(fù)制代碼 代碼如下:
(function foo(bar) {
alert(this);
!bar && foo(1); // "should" be special object, but always (correct) global
})(); // global
this在作為構(gòu)造器調(diào)用的函數(shù)中的值
還有一個(gè)在函數(shù)的上下文中與this的值相關(guān)的情況是:函數(shù)作為構(gòu)造器調(diào)用時(shí)。
復(fù)制代碼 代碼如下:
function A() {
alert(this); // newly created object, below - "a" object
this.x = 10;
}
var a = new A();
alert(a.x); // 10
在這個(gè)例子中,new操作符調(diào)用“A”函數(shù)內(nèi)部的[[Construct]]方法,接著,在對象創(chuàng)建后,調(diào)用其內(nèi)部的[[Call]]方法,所有相同的函數(shù)“A”都將this的值設(shè)置為新創(chuàng)建的對象。
手動(dòng)設(shè)置一個(gè)函數(shù)調(diào)用的this
在Function.prototype中定義了兩個(gè)方法允許手動(dòng)設(shè)置函數(shù)調(diào)用時(shí)this的值,它們是.apply和.call方法(所有的函數(shù)都可以訪問它們)。它們用接受的第一個(gè)參數(shù)作為this的值,this在調(diào)用的作用域中使用。這兩個(gè)方法的區(qū)別不大,對于.apply,第二個(gè)參數(shù)必須是數(shù)組(或者是類似數(shù)組的對象,如arguments,相反,.call能接受任何參數(shù)。兩個(gè)方法必須的參數(shù)都是第一個(gè)——this。
例如
復(fù)制代碼 代碼如下:
var b = 10;
function a(c) {
alert(this.b);
alert(c);
}
a(20); // this === global, this.b == 10, c == 20
a.call({b: 20}, 30); // this === {b: 20}, this.b == 20, c == 30
a.apply({b: 30}, [40]) // this === {b: 30}, this.b == 30, c == 40
結(jié)論
在這篇文章中,我們討論了ECMAScript中this關(guān)鍵字的特征(and they really are features, in contrast, say, with C++ or Java,譯者注:這句話沒什么大用,還不知道咋翻好,暫不翻了)。我希望這篇文章有助于你準(zhǔn)確的理解ECMAScript中this關(guān)鍵字如何工作。同樣,我很高興在評論中回答您的問題。
其他參考
10.1.7 – This;
11.1.1 – The this keyword;
11.2.2 – The new operator;
11.2.3 – Function calls.
英文地址 : ECMA-262-3 in detail. Chapter 3. This.
中文地址 : [JavaScript]ECMA-262-3 深入解析.第三章.this
翻譯聲明:
1.因?yàn)镈enis已經(jīng)翻譯過這篇文章,所以該篇譯文在部分章節(jié)參考了他的譯文,參考引用部分大概占整篇文章的30%左右,另外70%左右完全是重新翻譯的。
2.在翻譯過程中,跟原作者進(jìn)行了充分的溝通,大家看譯文的時(shí)候,可以多參考原文的留言列表。
3.再好的翻譯也趕不上原汁原味的原文,所以推薦大家看過譯文之后還是要再仔細(xì)看看原文。
您可能感興趣的文章:
- JavaScript ECMA-262-3 深入解析(一):執(zhí)行上下文實(shí)例分析
- JavaScript高級程序設(shè)計(jì) 閱讀筆記(七) ECMAScript中的語句
- JavaScript高級程序設(shè)計(jì)閱讀筆記(六) ECMAScript中的運(yùn)算符(二)
- JavaScript高級程序設(shè)計(jì)閱讀筆記(五) ECMAScript中的運(yùn)算符(一)
- JavaScript高級程序設(shè)計(jì) 閱讀筆記(四) ECMAScript中的類型轉(zhuǎn)換
- 《JavaScript高級程序設(shè)計(jì)》閱讀筆記(三) ECMAScript中的引用類型
- 《JavaScript高級程序設(shè)計(jì)》閱讀筆記(二) ECMAScript中的原始類型
- 《JavaScript高級程序設(shè)計(jì)》閱讀筆記(一) ECMAScript基礎(chǔ)
- JavaScript ECMA-262-3 深入解析(二):變量對象實(shí)例詳解
相關(guān)文章
Express與NodeJs創(chuàng)建服務(wù)器的兩種方法
本文主要介紹了NodeJs創(chuàng)建Web服務(wù)器;Express創(chuàng)建Web服務(wù)器的兩種方法,具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02ES6學(xué)習(xí)筆記之let、箭頭函數(shù)和剩余參數(shù)
ES6為我們在函數(shù)的使用上也提供了許多的便捷的東西,下面這篇文章主要給大家介紹了關(guān)于ES6學(xué)習(xí)筆記之let、箭頭函數(shù)和剩余參數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09JavaScript碰撞檢測原理及其實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了JavaScript碰撞檢測原理及其實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03jQuery實(shí)現(xiàn)可收縮展開的級聯(lián)菜單實(shí)例代碼
這篇文章主要是對利用jQuery實(shí)現(xiàn)可收縮展開的級聯(lián)菜單的實(shí)例代碼進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-11-11微信小程序bindtap與catchtap的區(qū)別詳解
本文主要介紹了微信小程序bindtap與catchtap的區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09javascript實(shí)例--教你實(shí)現(xiàn)撲克牌洗牌功能
我們一般都會(huì)按照順序把隨機(jī)摸過來的牌從小到大的順序在手上理整齊(記得小時(shí)候打牌兩副牌手都抓不過來),這篇隨筆就是想通過實(shí)現(xiàn)這個(gè)功能來熟悉下js中排序數(shù)組等相關(guān)知識(shí)。2014-05-05微信小程序適配iphoneX的實(shí)現(xiàn)方法
這篇文章主要介紹了微信小程序適配iphoneX的實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09淺析JavaScript中的同名標(biāo)識(shí)符優(yōu)先級
這篇文章主要介紹了JavaScript中的同名標(biāo)識(shí)符優(yōu)先級。需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12