前言
this是JavaScript中的著名月經(jīng)題,每隔一段時間就有人翻出了拿各種奇怪的問題出來討論,每次都會引發(fā)一堆口水之爭。從搜索引擎搜了一下現(xiàn)在的比較熱門的關(guān)于this的用法,如: Javascript的this用法 、 深入理解JavaScript中的this關(guān)鍵字 、 你不知道的this 等文章幾乎都是從現(xiàn)象出發(fā),總結(jié)this在不同場景下的指向結(jié)果,如同 江湖郎中 一般,都沒有從根本上解釋現(xiàn)象出現(xiàn)的原因,這就導(dǎo)致每次有了關(guān)于this的題層出不窮,因?yàn)榻?jīng)驗(yàn)總結(jié)只是教會了你現(xiàn)有的場景,而沒有教會你如何解釋新的場景。
老司機(jī)都知道,發(fā)展到今天,有規(guī)范在,有源碼在,早已經(jīng)不是IE6時代,還需要總結(jié)使用經(jīng)驗(yàn)場景也太不科學(xué)了。最近又在網(wǎng)上刷到關(guān)于this的討論,正巧在規(guī)范中追尋過this的秘密,在這里分享一下個人經(jīng)驗(yàn)。
* 以下規(guī)范均引用ES5
理論篇
規(guī)范中之處ECMAScript有三種可執(zhí)行代碼:
全局代碼( Global code )
eval代碼( Eval code )
函數(shù)代碼( Function code )
其中,對于全局代碼直接指向global object ,eval代碼由于已經(jīng)不推薦使用暫不做討論,我們主要關(guān)注函數(shù)代碼中的 this 如何指定。
進(jìn)入函數(shù)代碼
The following steps are performed when control enters the execution context for function code contained in function object F, a caller provided thisArg, and a caller provided argumentsList
規(guī)范指出,當(dāng)執(zhí)行流進(jìn)入函數(shù)代碼時,由 函數(shù)調(diào)用者 提供 thisArg 和 argumentsList 。所以我們需要繼續(xù)尋找,查看函數(shù)調(diào)用時候this是如何傳遞進(jìn)去的。
函數(shù)調(diào)用
The production CallExpression : MemberExpression Arguments is evaluated as follows:
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.
從上述規(guī)范中,在函數(shù)調(diào)用發(fā)生時,step 1首先會對函數(shù)名部分進(jìn)行計(jì)算并賦值給 ref 。
step 6、7中 幾個if else就決定了一個函數(shù)調(diào)用發(fā)生時,this會指向何方。

在套用if else之前,我們還需要稍微了解一下 Type(ref) is Reference 中的 Reference是個什么東東。
Reference type
Reference type按字面翻譯就是引用類型,但是它并不是我們常說的JavaScript中的引用類型,它是一個 規(guī)范類型 (實(shí)際并不存在),也就是說是為了解釋規(guī)范某些行為而存在的,比如delete、typeof、賦值語句等。規(guī)范類型設(shè)計(jì)用于解析命名綁定的,它由三部分組成:
base value,指向引用的原值
referenced name,引用的名稱
strict reference flag,標(biāo)示是否嚴(yán)格模式
用大白話講,就是規(guī)范中定義了一種類型叫做Reference用來引用其他變量,它有一個規(guī)定的數(shù)據(jù)結(jié)構(gòu)。由于是規(guī)范類型,所以什么情況下會返回Reference規(guī)范上也會寫得一清二楚。
至此,我們就可以直接開始實(shí)戰(zhàn)了 ^ ^
實(shí)戰(zhàn)篇
我們通過上述理論來解釋下面代碼中的this:
foo();
foo.bar();
(f = foo.bar)();
如何解釋foo()
1 . 執(zhí)行函數(shù)調(diào)用規(guī)范中的step1:
Let ref be the result of evaluating MemberExpression.
MemberExpression就是括號左邊的部分,此處很簡單就是 foo。foo我們知道就是一個標(biāo)示符,那么執(zhí)行foo的時候會發(fā)生什么呢? 答案都在規(guī)范中 :
11.1.2 Identifier Reference
An Identifier is evaluated by performing Identifier Resolution as specified in 10.3.1. The result of evaluating anIdentifier is always a value of type Reference.
標(biāo)示符被執(zhí)行的時候會進(jìn)行標(biāo)示符解析(Identifier Resolution),看10.3.1。(連章節(jié)都標(biāo)好了好伐,只需要點(diǎn)過去就行了。)
10.3.1 Identifier Resolution
1.Let env be the running execution context’s LexicalEnvironment.
…
3.Return the result of calling GetIdentifierReference function passing env, Identifier, and strict as arguments.
看這個返回,返回了 GetIdentifierReference 方法的結(jié)果,所以我們還需要再走一步(要有耐心 - -)