<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,如需轉載請自行聯系原作者