<a href="http://www.cnblogs.com/justinw/archive/2010/05/04/1727295.html#introduction">介绍</a>
<a href="http://www.cnblogs.com/justinw/archive/2010/05/04/1727295.html#definitions">定义</a>
<a href="http://www.cnblogs.com/justinw/archive/2010/05/04/1727295.html#this-value-in-the-global-code">this在全局代码中的值</a>
<a href="http://www.cnblogs.com/justinw/archive/2010/05/04/1727295.html#this-value-in-the-function-code">this在函数代码中的值</a>
<a href="http://www.cnblogs.com/justinw/archive/2010/05/04/1727295.html#conclusion">结论</a>
<a href="http://www.cnblogs.com/justinw/archive/2010/05/04/1727295.html#-additional-literature">其他参考</a>
在这篇文章里,我们将讨论跟执行上下文直接相关的更多细节。讨论的主题就是this关键字。
实践证明,这个主题很难,在不同执行上下文中确定this的值经常会发生问题。
许多程序员习惯的认为,在程序语言中,this关键字与面向对象程序开发紧密相关,其完全指向由构造器新创建的对象。在ECMAScript规范中也是这样实现的,但正如我们将看到那样,在ECMAScript中,this并不限于只用来指向新创建的对象。
下面让我们更详细的了解一下,在ECMAScript中this的值到底是什么?
this是执行上下文中的一个属性:
1
2
3
4
<code>activeExecutionContext = {</code>
<code> </code><code>VO: {...},</code>
<code> </code><code>this</code><code>: thisValue</code>
<code>};</code>
下面让我们更详细研究这些场景。
在这里一切都很简单。在全局代码中,this始终是全局对象本身,这样就有可能间接的引用到它了。
5
6
7
8
9
10
11
12
13
14
15
<code>// explicit property definition of</code>
<code>// the global object</code>
<code>this</code><code>.a = 10;</code><code>// global.a = 10</code>
<code>alert(a);</code><code>// 10</code>
<code> </code>
<code>// implicit definition via assigning</code>
<code>// to unqualified identifier</code>
<code>b = 20;</code>
<code>alert(</code><code>this</code><code>.b);</code><code>// 20</code>
<code>// also implicit via variable declaration</code>
<code>// because variable object of the global context</code>
<code>// is the global object itself</code>
<code>var</code> <code>c = 30;</code>
<code>alert(</code><code>this</code><code>.c);</code><code>// 30</code>
在函数代码中使用this时很有趣,这种应用场景很难且会导致很多问题。
在这种类型的代码中,this值的首要(也许是最主要的)特点是它没有静态绑定到一个函数。
正如我们上面曾提到的那样,this的值在进入上下文时确定,在函数代码中,this的值每一次(进入上下文时)可能完全不同。
不管怎样,在代码运行期间,this的值是不变的,也就是说,因为this不是一个变量,所以不可能为其分配一个新值。(相反,在Python编程语言中,它明确的定义为对象本身,在运行期间可以不断改变)。
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<code>var</code> <code>foo = {x: 10};</code>
<code>var</code> <code>bar = {</code>
<code> </code><code>x: 20,</code>
<code> </code><code>test:</code><code>function</code> <code>() {</code>
<code> </code><code>alert(</code><code>this</code> <code>=== bar);</code><code>// true</code>
<code> </code><code>alert(</code><code>this</code><code>.x);</code><code>// 20</code>
<code> </code><code>this</code> <code>= foo;</code><code>// error</code>
<code> </code><code>alert(</code><code>this</code><code>.x);</code><code>// if there wasn't an error then 20, not 10</code>
<code> </code><code>}</code>
<code>// on entering the context this value is</code>
<code>// determined as "bar" object; why so - will</code>
<code>// be discussed below in detail</code>
<code>bar.test();</code><code>// true, 20</code>
<code>foo.test = bar.test;</code>
<code>// however here this value will now refer</code>
<code>// to "foo" – even though we're calling the same function</code>
<code>foo.test();</code><code>// false, 10</code>
那么,在函数代码中,什么影响了this的值发生变化?有几个因素。
为了在任何情况下准确无误的确定this值,有必要理解和记住这重要的一点:正是调用函数的方式影响了调用的上下文中this的值,没有别的什么(我们可以在一些文章,甚至是在关于javascript的书籍中看到,它们声称:“this的值取决于函数如何定义,如果它是全局函数,this设置为全局对象,如果函数是一个对象的方法,this将总是指向这个对象。–这绝对不正确”)。继续我们的话题,可以看到,即使是正常的全局函数也会因为不同调用方式而激活,这些不同调用方式产生了this不同的值。
<code>function</code> <code>foo() {</code>
<code> </code><code>alert(</code><code>this</code><code>);</code>
<code>}</code>
<code>foo();</code><code>// global</code>
<code>alert(foo === foo.prototype.constructor);</code><code>// true</code>
<code>// but with another form of the call expression</code>
<code>// of the same function, this value is different</code>
<code>foo.prototype.constructor();</code><code>// foo.prototype</code>
有时可能将函数作为某些对象的一个方法来调用,此时this的值不会设置为这个对象。
<code>var</code> <code>foo = {</code>
<code> </code><code>bar:</code><code>function</code> <code>() {</code>
<code> </code><code>alert(</code><code>this</code><code>);</code>
<code> </code><code>alert(</code><code>this</code> <code>=== foo);</code>
<code>foo.bar();</code><code>// foo, true</code>
<code>var</code> <code>exampleFunc = foo.bar;</code>
<code>alert(exampleFunc === foo.bar);</code><code>// true</code>
<code>// again with another form of the call expression</code>
<code>// of the same function, we have different this value</code>
<code>exampleFunc();</code><code>// global, false</code>
那么,到底调用函数的方式如何影响this的值?为了充分理解this的值是如何确定的,我们需要详细分析一个内部类型(internal type)——引用类型(Reference type)。
用伪代码可以把引用类型表示为拥有两个属性的对象——base(即拥有属性的那个对象),和base中的propertyName 。
<code>var</code> <code>valueOfReferenceType = {</code>
<code> </code><code>base: <base object>,</code>
<code> </code><code>propertyName: <property name></code>
引用类型的值仅存在于两种情况中:
1. 当我们处理一个标示符时;(when we deal with an identifier;)
2. 或一个属性访问器;(or with a property accessor.)
标识符是变量名,函数名,函数参数名和全局对象中未识别的属性名。例如,下面标识符的值:
<code>var</code> <code>foo = 10;</code>
<code>function</code> <code>bar() {}</code>
在操作的中间结果中,引用类型对应的值如下:
<code>var</code> <code>fooReference = {</code>
<code> </code><code>base: global,</code>
<code> </code><code>propertyName:</code><code>'foo'</code>
<code>var</code> <code>barReference = {</code>
<code> </code><code>propertyName:</code><code>'bar'</code>
<code>function</code> <code>GetValue(value) {</code>
<code> </code><code>if</code> <code>(Type(value) != Reference) {</code>
<code> </code><code>return</code> <code>value;</code>
<code> </code><code>var</code> <code>base = GetBase(value);</code>
<code> </code><code>if</code> <code>(base ===</code><code>null</code><code>) {</code>
<code> </code><code>throw</code> <code>new</code> <code>ReferenceError;</code>
<code> </code><code>return</code> <code>base.[[Get]](GetPropertyName(value));</code>
内部的[[Get]]方法返回对象属性真正的值,包括对原型链中继承属性的分析。
<code>GetValue(fooReference);</code><code>// 10</code>
<code>GetValue(barReference);</code><code>// function object "bar"</code>
属性访问器都应该熟悉。它有两种变体:点(.)语法(此时属性名是正确的标示符,且事先知道),或括号语法([])。
<code>foo.bar();</code>
<code>foo[</code><code>'bar'</code><code>]();</code>
在计算中间的返回值中,引用类型对应的值如下:
<code>var</code> <code>fooBarReference = {</code>
<code> </code><code>base: foo,</code>
<code>GetValue(fooBarReference);</code><code>// function object "bar"</code>
那么,从最重要的意义上来说,引用类型的值与函数上下文中的this的值是如何关联起来的呢?这个关联的过程是这篇文章的核心。(The given moment is the main of this article.) 在一个函数上下文中确定this的值的通用规则如下:
在一个函数上下文中,this的值由调用者提供,且由调用函数的方式决定。如果调用括号()的左边是引用类型的值,this将设为这个引用类型值的base对象,在其他情况下(与引用类型不同的任何其它属性),this的值都为null。不过,实际不存在this的值为null的情况,因为当this的值为null的时候,其值会被隐式转换为全局对象。
下面让我们看个例子:
<code> </code><code>return</code> <code>this</code><code>;</code>
我们看到在调用括号的左边是一个引用类型值(因为foo是一个标示符):
相应地,this也设置为引用类型的base对象。即全局对象。
同样,使用属性访问器:
<code> </code><code>return</code> <code>this</code><code>;</code>
<code>foo.bar();</code><code>// foo</code>
同样,我们拥有一个引用类型的值,其base是foo对象,在函数bar激活时将base设置给this。
但是,如果用另一种方式激活相同的函数,this的值将不同。
<code>var</code> <code>test = foo.bar;</code>
<code>test();</code><code>// global</code>
因为test作为标识符,产生了其他引用类型的值,该值的base(全局对象)被设置为this的值。
<code>var</code> <code>testReference = {</code>
<code> </code><code>propertyName:</code><code>'test'</code>
现在,我们可以很明确的说明,为什么用不同的形式激活同一个函数会产生不同的this,答案在于不同的引用类型(type Reference)的中间值。
<code>foo();</code><code>// global, because</code>
<code>// another form of the call expression</code>
<code>foo.prototype.constructor();</code><code>// foo.prototype, because</code>
<code>var</code> <code>fooPrototypeConstructorReference = {</code>
<code> </code><code>base: foo.prototype,</code>
<code> </code><code>propertyName:</code><code>'constructor'</code>
另一个通过调用方式动态确定this的值的经典例子:
<code> </code><code>alert(</code><code>this</code><code>.bar);</code>
<code>var</code> <code>x = {bar: 10};</code>
<code>var</code> <code>y = {bar: 20};</code>
<code>x.test = foo;</code>
<code>y.test = foo;</code>
<code>x.test();</code><code>// 10</code>
<code>y.test();</code><code>// 20</code>
那么,正如我们已经指出,当调用括号的左边不是引用类型而是其它类型,this的值自动设置为null,实际最终this的值被隐式转换为全局对象。
让我们思考下面这种函数表达式:
<code>(</code><code>function</code> <code>() {</code>
<code> </code><code>alert(</code><code>this</code><code>);</code><code>// null => global</code>
<code>})();</code>
在这个例子中,我们有一个函数对象但不是引用类型的对象(因为它不是标示符,也不是属性访问器),相应地,this的值最终被设为全局对象。
更多复杂的例子:
<code>foo.bar();</code><code>// Reference, OK => foo</code>
<code>(foo.bar)();</code><code>// Reference, OK => foo</code>
<code>(foo.bar = foo.bar)();</code><code>// global?</code>
<code>(</code><code>false</code> <code>|| foo.bar)();</code><code>// global?</code>
<code>(foo.bar, foo.bar)();</code><code>// global?</code>
那么,为什么我们有一个属性访问器,它的中间值应该为引用类型的值,但是在某些调用中我们得到this的值不是base对象,而是global对象?
问题出现在后面的三个调用,在执行一定的操作运算之后,在调用括号的左边的值不再是引用类型。
第一个例子很明显———明显的引用类型,结果是,this为base对象,即foo。
第四个和第五个也是一样——逗号操作符和逻辑操作符(OR)调用了GetValue 方法,相应地,我们失去了引用类型的值而得到了函数类型的值,所以this的值再次被设为global对象。
有一种情况,如果调用方式确定了引用类型的值(when call expression determinates on the left hand side of call brackets the value of Reference type。译者注,原文有点拖沓!),不管怎样,只要this的值被设置为null,其最终就会被隐式转换成global。当引用类型值的base对象是激活对象时,就会导致这种情况。
<code> </code><code>function</code> <code>bar() {</code>
<code> </code><code>alert(</code><code>this</code><code>);</code><code>// global</code>
<code> </code><code>bar();</code><code>// the same as AO.bar()</code>
有一种情况除外:“在with语句中调用函数,且在with对象(译者注:即下面例子中的__withObject)中包含函数名属性时”。with语句将其对象添加在作用域链最前端,即在激活对象的前面。那么对应的,引用类型有值(通过标识符或属性访问器),其base对象不再是激活对象,而是with语句的对象。顺便提一句,这种情况不仅跟内部函数相关,还跟全局函数相关,因为with对象比作用域链里的最前端的对象(全局对象或一个激活对象)还要靠前。
<code>var</code> <code>x = 10;</code>
<code>with</code> <code>({</code>
<code> </code><code>foo:</code><code>function</code> <code>() {</code>
<code> </code><code>alert(</code><code>this</code><code>.x);</code>
<code> </code><code>},</code>
<code> </code><code>x: 20</code>
<code>}) {</code>
<code> </code><code>foo();</code><code>// 20</code>
<code>// because</code>
<code>var</code> <code>fooReference = {</code>
<code> </code><code>base: __withObject,</code>
在catch语句的实际参数中的函数调用存在类似情况:在这种情况下,catch对象被添加到作用域的最前端,即在激活对象或全局对象的前面。但是,这个特定的行为被确认为是ECMA-262-3的一个bug,这个在新版的ECMA-262-5中修复了。修复后,在特定的激活对象中,this指向全局对象。而不是catch对象。
<code>try</code> <code>{</code>
<code> </code><code>throw</code> <code>function</code> <code>() {</code>
<code> </code><code>};</code>
<code>}</code><code>catch</code> <code>(e) {</code>
<code> </code><code>e();</code><code>// __catchObject - in ES3, global - fixed in ES5</code>
<code>// on idea</code>
<code>var</code> <code>eReference = {</code>
<code> </code><code>base: __catchObject,</code>
<code> </code><code>propertyName:</code><code>'e'</code>
<code>// but, as this is a bug</code>
<code>// then this value is forced to global</code>
<code>// null => global</code>
<code>(</code><code>function</code> <code>foo(bar) {</code>
<code> </code><code>!bar && foo(1);</code><code>// "should" be special object, but always (correct) global</code>
<code>})();</code><code>// global</code>
还有一个在函数的上下文中与this的值相关的情况是:函数作为构造器调用时。
<code>function</code> <code>A() {</code>
<code> </code><code>alert(</code><code>this</code><code>);</code><code>// newly created object, below - "a" object</code>
<code> </code><code>this</code><code>.x = 10;</code>
<code>var</code> <code>a =</code><code>new</code> <code>A();</code>
<code>alert(a.x);</code><code>// 10</code>
在Function.prototype中定义了两个方法允许手动设置函数调用时this的值,它们是.apply和.call方法(所有的函数都可以访问它们)。它们用接受的第一个参数作为this的值,this在调用的作用域中使用。这两个方法的区别不大,对于.apply,第二个参数必须是数组(或者是类似数组的对象,如arguments,相反,.call能接受任何参数。两个方法必须的参数都是第一个——this。
例如
<code>var</code> <code>b = 10;</code>
<code>function</code> <code>a(c) {</code>
<code> </code><code>alert(</code><code>this</code><code>.b);</code>
<code> </code><code>alert(c);</code>
<code>a(20);</code><code>// this === global, this.b == 10, c == 20</code>
<code>a.call({b: 20}, 30);</code><code>// this === {b: 20}, this.b == 20, c == 30</code>
<code>a.apply({b: 30}, [40])</code><code>// this === {b: 30}, this.b == 30, c == 40</code>
在这篇文章中,我们讨论了ECMAScript中this关键字的特征(and they really are features, in contrast, say, with C++ or Java,译者注:这句话没什么大用,还不知道咋翻好,暂不翻了)。我希望这篇文章有助于你准确的理解ECMAScript中this关键字如何工作。同样,我很高兴在评论中回答您的问题。
翻译声明:
2.在翻译过程中,跟原作者进行了充分的沟通,大家看译文的时候,可以多参考原文的留言列表。
3.再好的翻译也赶不上原汁原味的原文,所以推荐大家看过译文之后还是要再仔细看看原文。
本文转自Justin博客园博客,原文链接:http://www.cnblogs.com/justinw/archive/2010/05/04/1727295.html,如需转载请自行联系原作者