JavaScript深入之從ECMAScript規范解讀this
前言
在《JavaScript深入之執行上下文棧》中講到,當JavaScript代碼執行一段可執行代碼(executable code)時,會創建對應的執行上下文(execution context)。
對于每個執行上下文,都有三個重要屬性
- 變量對象(Variable object,VO)
- 作用域鏈(Scope chain)
- this
今天重點講講this,然而不好講。
……
因為我們要從ECMASciript5規范開始講起。
讓我們開始簡單的了解規范吧!
Types
首先是第8章Types:
引用
Types are further subclassified into ECMAScript language types and specification types.
An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.
我們簡單的翻譯一下:
ECMAScript的類型分為語言類型和規范類型。
ECMAScript語言類型是開發者直接使用ECMAScript可以操作的。其實就是我們常說的Undefined, Null, Boolean, String, Number, 和 Object。
而規范類型相當于meta-values,是用來用算法描述ECMAScript語言結構和ECMAScript語言類型的。規范類型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。
沒懂?沒關系,我們重點看其中的Reference類型。
Reference
那什么又是Reference?
讓我們看8.7章 The Reference Specification Type:
引用
The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.
所以Reference類型就是用來解釋諸如delete、typeof以及賦值等操作行為的。
抄襲尤雨溪大大的話,就是:
引用
這里的 Reference 是一個 Specification Type,也就是 “只存在于規范里的抽象類型”。它們是為了更好地描述語言的底層行為邏輯才存在的,但并不存在于實際的 js 代碼中。
再看接下來的這段具體介紹Reference的內容:
引用
A Reference is a resolved name binding.
A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag.
The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).
A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.
這段講了Reference有三個組成部分,分別是:
- base value
- referenced name
- strict reference
而且base value是undefined, an Object, a Boolean, a String, a Number, or an environment record其中的一種reference name是字符串。
可是這些到底是什么呢?
讓我們簡潔的理解base value是屬性所在的對象或者就是EnvironmentRecord,referenced name就是屬性的名稱
嗯,舉個例子:
var foo = 1;
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
再舉個例子:
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
var fooBarReference = {
base: foo,
propertyName: 'bar',
strict: false
};
而且規范中還提供了可以獲取Reference組成部分的方法,比如 GetBase 和 IsPropertyReference
這兩個方法很簡單,簡單看一看:
1.GetBase
引用
GetBase(V). Returns the base value component of the reference V.
返回reference的base value
2.IsPropertyReference
引用
IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.
簡單的理解:base value是object,就返回true
GetValue
除此之外,緊接著規范中就講了一個GetValue方法,在8.7.1章
簡單模擬GetValue的使用:
var foo = 1;
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
GetValue(fooReference) // 1;
GetValue返回對象屬性真正的值,但是要注意, 調用GetValue,返回的將是具體的值,而不再是一個Reference ,這個很重要。
那為什么要講References呢?
如何確定this的值
看規范11.2.3 Function Calls。
這里講了當函數調用的時候,如何確定this的取值
看第一步 第六步 第七步:
引用
1.Let ref be the result of evaluating MemberExpression.
6.If Type(ref) is Reference, then
a.If IsPropertyReference(ref) is true, then
i.Let thisValue be GetBase(ref).
b.Else, the base of ref is an Environment Record
i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
7.Else, Type(ref) is not Reference.
a. Let thisValue be undefined.
讓我們描述一下:
1.計算MemberExpression的結果賦值給ref
2.判斷ref是不是一個Reference類型,
2.1.如果ref是Reference,并且IsPropertyReference(ref)是true, 那么this = GetBase(ref) 2.2.如果ref是Reference,并且base值是Environment Record, 那么this = ImplicitThisValue(ref), 2.3.如果ref不是Reference,那么 this = undefined
讓我們一步一步看:
1、計算MemberExpression
什么是MemberExpression?看規范11.2 Left-Hand-Side Expressions:
MemberExpression :
- PrimaryExpression // 原始表達式 可以參見《JavaScript權威指南第四章》
- FunctionExpression // 函數定義表達式
- MemberExpression [ Expression ] // 屬性訪問表達式
- MemberExpression . IdentifierName // 屬性訪問表達式
- new MemberExpression Arguments // 對象創建表達式
舉個例子:
function foo() {
console.log(this)
}
foo(); // MemberExpression是foo
function foo() {
return function() {
console.log(this)
}
}
foo()(); // MemberExpression是foo()
var foo = {
bar: function () {
return this;
}
}
foo.bar(); // MemberExpression是foo.bar
所以簡單理解MemberExpression其實就是()左邊的部分
接下來就是判斷MemberExpression的結果是不是Reference,這時候就要看規范是如何處理各種MemberExpression,看規范規定這些操作是不是會返回一個Reference類型。
舉最后一個例子:
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
//試驗1
console.log(foo.bar());
//試驗2
console.log((foo.bar)());
//試驗3
console.log((foo.bar = foo.bar)());
//試驗4
console.log((false || foo.bar)());
//試驗5
console.log((foo.bar, foo.bar)());
在試驗1中,MemberExpression計算的結果是foo.bar,那么foo.bar是不是一個Reference呢?
查看規范11.2.1 Property Accessors,這里展示了一個計算的過程,什么都不管了,就看最后一步
引用
Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
返回了一個Reference類型!
該值為:
var Reference = {
base: foo,
name: 'bar',
strict: false
};
然后這個因為base value是一個對象,所以IsPropertyReference(ref)是true,
那么this = GetBase(ref),也就是foo, 所以this指向foo,試驗1的結果就是 2
唉呀媽呀,為了證明this指向foo,累死我了!
剩下的就很快了:
看試驗2,使用了()包住了foo.bar
查看規范11.1.6 The Grouping Operator
引用
Return the result of evaluating Expression. This may be of type Reference.
實際上()并沒有對MemberExpression進行計算,所以跟試驗1是一樣的。
看試驗3,有賦值操作符 查看規范11.13.1 Simple Assignment ( = ):
計算的第三步:
引用
3.Let rval be GetValue(rref).
因為使用了GetValue,所以返回的不是reference類型,this為undefined
看試驗4,邏輯云算法
查看規范11.11 Binary Logical Operators:
計算第二步:
引用
2.Let lval be GetValue(lref).
因為使用了GetValue,所以返回的不是reference類型,this為undefined
看試驗5,逗號操作符 查看規范11.14 Comma Operator ( , )
計算第二步:
引用
2.Call GetValue(lref).
因為使用了GetValue,所以返回的不是reference類型,this為undefined
但是注意在非嚴格模式下,this的值為undefined的時候,其值會被隱式轉換為全局對象。
所以最后一個例子的結果是:
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
//試驗1
console.log(foo.bar()); //2
//試驗2
console.log((foo.bar)()); //2
//試驗3
console.log((foo.bar = foo.bar)()); //1
//試驗4
console.log((false || foo.bar)()); //1
//試驗5
console.log((foo.bar, foo.bar)()); //1
注意:嚴格模式下因為this返回undefined,所以試驗3會報錯
最后,忘記了一個最最普通的情況:
function foo() {
console.log(this)
}
foo();
MemberExpression是foo,解析標識符 查看規范10.3.1 Identifier Resolution
會返回一個 Reference類型
但是 base value是 Environment Record,所以會調用ImplicitThisValue(ref)
查看規范10.2.1.1.6
始終返回undefined
所以最后this的值是undefined
最后
盡管我們不可能去確定每一個this的指向都從規范的角度去思考,久而久之,我們就會總結各種情形來告訴大家這種情形下this的指向,但是能從規范的角度去看待this的指向,絕對是一個不一樣的角度,該文還是有些晦澀難懂,希望大神指正!
來自:http://www.iteye.com/news/32326