在函數的調用中,this是個什麼東西,又是由什麼決定的呢?在ecma262中,這是個比較繞的東西,其描述散落在世界各地。
首先,在10.2.3中告訴我們: The caller provides the this value. If the this value provided by the caller is not an object (note that null is not an object), then the this value is the global object. 我們可以知道,caller可以提供給我們this。如果沒有提供,則this為global object。問題又來了,caller是怎麼提供this的?
在11.2.3中,找到如下關於Function calls的描述:The production CallExpression : MemberExpression Arguments is evaluated as follows:
1. Evaluate MemberExpression.
2. Evaluate Arguments, producing an internal list of argument values (see 11.2.4).
3. Call GetValue(Result(1)).
4. If Type(Result(3)) is not Object, throw a TypeError exception.
5. If Result(3) does not implement the internal [[Call]] method, throw a TypeError exception.
6. If Type(Result(1)) is Reference, Result(6) is GetBase(Result(1)). Otherwise, Result(6) is null.
7. If Result(6) is an activation object, Result(7) is null. Otherwise, Result(7) is the same as Result(6).
8. Call the [[Call]] method on Result(3), providing Result(7) as the this value and providing the list Result(2) as the argument values.
9. Return Result(8).
從步驟6、7中可以看出來,如果MemberExpression的結果是一個Reference的話,提供的this應該是 GetBase(Reference),否則是空。步驟7中還有描述了6的結果是使用中的物件的情況,我們這裡忽略。 又有疑問了,Reference?Reference是什麼,GetBase又是什嗎?
我們在8.7中,找到了Reference的答案。這裡的描述比較長,我只摘了可以滿足我們需要的一段: A Reference is a reference to a property of an object. A Reference consists of two components, the base object and the property name.
The following abstract operations are used in this specification to access the components of references:
GetBase(V). Returns the base object component of the reference V.
GetPropertyName(V). Returns the property name component of the reference V.
已經很明顯了,一個Reference必須引用一個對象的一個屬性。所以我們通過obj.method()來調用的時候,obj.method這個運算式產生了一個中間態的Reference,這個Reference的base object就是obj,所以GetBase的結果就是obj,於是obj被caller提供作this
我曾經看到很多文章,舉了類似obj.method()這樣的調用例子,認為obj就是caller,來解釋這番話:
The caller provides the this value. If the this value provided by the caller is not an object (note that null is not an object), then the this value is the global object.
這其實是說不通的。
caller絕不可能是obj,否則被attachEvent的函數或對象方法,他們運行時的this就解釋不通了。 所以,通過我們自己代碼調用的函數,caller由指令碼引擎執行控制所決定;在瀏覽器宿主環境通過事件觸發的,caller由瀏覽器控制的行為所決定。
------------------- 切割線:堅持堅持,好不容易想寫點正經東西,快了 ----------------------
9. 關於原型鏈的補充——原型鏈會不會是圓形鏈
這個問題是telei同學提出的。答案是:不會
回頭看看[[Construct]]的步驟,我們可以發現,建立一個對象obj時,obj.[[prototype]]成員被賦予其構造器的 prototype成員。但是當構造器的prototype成員被指向為另外一個對象的引用時,obj.[[prototype]]依然是其構造器的前 prototype對象。
描述代碼如下:(注釋裡是說明)
function A(){
this.testA = new Function();
}
function B(){
this.testB = new Function();
}
var a = new A();
B.prototype = a;
//a.[[prototype]] == {};(不是真的等,{}表示的是Function A初始的prototype object。下同)
var b = new B();
//b.[[prototype]] == a;
//b.[[prototype]].[[prototype]] == a.[[prototype]] == {};
A.prototype = b;
var a2 = new A();
//a2.[[prototype]] == b;
//a2.[[prototype]].[[prototype]] == b.[[prototype]] == a;
//a2.[[prototype]].[[prototype]].[[prototype]] == b.[[prototype]].[[prototype]] == a.[[prototype]] == {};
//最後測試一下,很搞笑的
alert(a instanceof A);
最後特殊的解釋:好吧,上面代碼的最後出現了很搞笑的事情,合乎語言的實現,但不合乎正常以及不正常地球人的邏輯。 我們知道,a對象是被A構造器建立出來的,所以a是A的執行個體。 但是,上面類型判斷那裡有講,instanceof是通過構造器prototype成員與對象原型鏈的比較來判斷的。所以當對象a被建立後,如果建立它的構造器的prototype發生了變化,a就和他媽(構造器)沒任何關係了。 看到這裡,你確定你還想要在執行個體化對象後,修改構造器的prototype成另外一個對象嗎?