摘要:執(zhí)行函數(shù)調(diào)用規(guī)范中的第一步是一個明顯的賦值語句,我們查看規(guī)范,賦值語句會發(fā)生什么可以看出簡單賦值語句返回的是對等于號右邊進行之后的結(jié)果,上一節(jié)講了,執(zhí)行過就會返回的類型,此處會返回也就是一個類型。
前言
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的題層出不窮,因為經(jīng)驗總結(jié)只是教會了你現(xiàn)有的場景,而沒有教會你如何解釋新的場景。
老司機都知道,發(fā)展到今天,有規(guī)范在,有源碼在,早已經(jīng)不是IE6時代,還需要總結(jié)使用經(jīng)驗場景也太不科學(xué)了。最近又在網(wǎng)上刷到關(guān)于this的討論,正巧在規(guī)范中追尋過this的秘密,在這里分享一下個人經(jīng)驗。
*以下規(guī)范均引用ES5
規(guī)范中之處ECMAScript有三種可執(zhí)行代碼:
全局代碼(Global code)
eval代碼(Eval code)
函數(shù)代碼(Function code)
其中,對于全局代碼直接指向global object,eval代碼由于已經(jīng)不推薦使用暫不做討論,我們主要關(guān)注函數(shù)代碼中的 this 如何指定。
進入函數(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í)行流進入函數(shù)代碼時,由函數(shù)調(diào)用者提供 thisArg 和 argumentsList。所以我們需要繼續(xù)尋找,查看函數(shù)調(diào)用時候this是如何傳遞進去的。
函數(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ā)生時,首先會對函數(shù)名部分進行計算并賦值給 ref ,6、7中
幾個if else就決定了一個函數(shù)調(diào)用發(fā)生時,this會指向何方。
在套用if else之前,我們還需要稍微了解一下 Type(ref) is Reference 中的 Reference是個什么東東。
Reference typeReference type按字面翻譯就是引用類型,但是它并不是我們常說的JavaScript中的引用類型,它是一個規(guī)范類型(實際并不存在),也就是說是為了解釋規(guī)范某些行為而存在的,比如delete、typeof、賦值語句等。規(guī)范類型設(shè)計用于解析命名綁定的,它由三部分組成:
base value,指向引用的原值
referenced name,引用的名稱
strict reference flag,標(biāo)示是否嚴格模式
用大白話講,就是規(guī)范中定義了一種類型叫做Reference用來引用其他變量,它有一個規(guī)定的數(shù)據(jù)結(jié)構(gòu)。由于是規(guī)范類型,所以什么情況下會返回Reference規(guī)范上也會寫得一清二楚。
至此,我們就可以直接開始實戰(zhàn)了 ^ ^
我們通過上述理論來解釋下面代碼中的this:
foo();
foo.bar();
(f = foo.bar)();
如何解釋foo();1 執(zhí)行函數(shù)調(diào)用規(guī)范中的第一步:
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í)行的時候會進行標(biāo)示符解析(Identifier Resolution),看10.3.1。(連章節(jié)都標(biāo)好了好伐,只需要點過去就行了。)
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é)果,所以我們還需要再走一步(要有耐心 - -)
GetIdentifierReference
Return a value of type Reference whose base value is envRec, whose referenced name is name, and whose strict mode flag is strict.
我們看此處返回了一個Reference , base value 是 envRec 也就是 10.3.1中傳入的 execution context’s LexicalEnvironment
(詞法環(huán)境為環(huán)境記錄項Environment Record的組成,它是規(guī)范用來管理當(dāng)前作用域下面變量的類型,此處不用理解,知道它返回了這個東東就行了)
其抽象數(shù)據(jù)結(jié)構(gòu)大概為:
foo_reference = { "base": LexicalEnvironment, "name": "foo", "strict": false }
至此,第一步執(zhí)行完畢,ref = foo_reference。
2 因為是reference, 執(zhí)行6:
Type(ref) is Reference
3 因為是Environment Record,執(zhí)行 6.b:
Else, the base of ref is an Environment Record
Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
4 ImplicitThisValue
最后,thisValue等于執(zhí)行ImplicitThisValue的結(jié)果,還是查看規(guī)范:
10.2.1.1.6 ImplicitThisValue()
Declarative Environment Records always return undefined as their ImplicitThisValue.
到此我們知道,foo()執(zhí)行時, thisValue = undefined; , 對應(yīng)到 JavaScript 代碼中的 this,還差最后一步:
Else if thisArg is null or undefined, set the ThisBinding to the global object.
真相大白:foo()執(zhí)行時,this = global = window
如何解釋 foo.bar()1 執(zhí)行函數(shù)調(diào)用規(guī)范中的第一步:
Let ref be the result of evaluating MemberExpression.
foo.bar我們都知道是一個屬性訪問,那么執(zhí)行屬性訪問的時候會發(fā)生什么呢?答案都在規(guī)范中:
11.2.1 Property Accessors
The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:
1.Let baseReference be the result of evaluating MemberExpression.
2.Let baseValue be GetValue(baseReference)
8.Return a value of type Reference whose base value is baseValue and whose referenced name ispropertyNameString, and whose strict mode flag is strict
baseReference 等于執(zhí)行 MemberExpression 在此處為 []左邊的語句即 foo 的結(jié)果,上一節(jié)已經(jīng)說過了返回一個 reference.
baseValue 等于 GetValue(baseReference) 。
[8.7.1 GetValue](http://es5.github.io/#x8.7.1) ( GetValue屬于一個規(guī)范中比較重要的方法,此處為節(jié)約篇幅,暫時不講,以后可以單開文章講解。暫時記住他的結(jié)果一般為:如果是reference傳入,會返回一個普通類型出來。比如 foo 為reference,通過GetValue之后就是一個普通的 object,也就是 foo 對應(yīng)的 js 類型本身。)
reference_foo_bar = { "base": foo, "name": "bar", "strict": false }
2 因為是reference, 執(zhí)行6:
Type(ref) is Reference
3 因為是屬性引用類型,執(zhí)行 6.a:
If IsPropertyReference(ref) is true, then
i.Let thisValue be GetBase(ref).
GetBase(ref) = reference_foo_bar.base = foo ;
真相大白, foo.bar() 執(zhí)行時 this 指向 foo
如何解釋 (f = foo.bar)()這個語句屬于罕見寫法,在各種經(jīng)驗總結(jié)中屬于高級技巧,但是通過規(guī)范,一樣分分鐘搞定。
1 執(zhí)行函數(shù)調(diào)用規(guī)范中的第一步:
Let ref be the result of evaluating MemberExpression.
f = foo.bar 是一個明顯的賦值語句,我們查看規(guī)范,賦值語句會發(fā)生什么:
[11.13.1 Simple Assignment ( = )] (http://es5.github.io/#x11.13.1)
The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:
Let rref be the result of evaluating AssignmentExpression.
Let rval be GetValue(rref)
Return rval.
可以看出簡單賦值語句返回的是對等于號右邊進行GetValue之后的結(jié)果,上一節(jié)講了,執(zhí)行過GetValue就會返回js的類型,此處會返回 foo.bar 也就是一個 [Object Function] 類型。
2 ref不是reference,執(zhí)行7
7.Else, Type(ref) is not Reference.
Let thisValue be undefined.
3 thisValue = undefined
同理:
Else if thisArg is null or undefined, set the ThisBinding to the global object.
真相大白, (f = foo.bar)() 執(zhí)行時 this = global = window
老司機的經(jīng)驗雖然我們不是江湖郎中,但是每次查詢規(guī)范也有點麻煩,此處還是有一定規(guī)律可循的。
我們觀察上述過程,其實最關(guān)鍵的就是判斷返回值是不是 reference ,如果不是,直接可以推出等于window,如果是,只需要看是不是屬性 reference。這里有外國友人統(tǒng)計過一張速查表:
Example | Reference? | Notes |
---|---|---|
"foo" | No | |
123 | No | |
/x/ | No | |
({}) | No | |
(function(){}) | No | |
foo | Yes | Could be unresolved reference if foo is not defined |
foo.bar | Yes | Property reference |
(123).toString | Yes | Property reference |
(function(){}).toString | Yes | Property reference |
(1,foo.bar) | No | Already evaluated, BUT see grouping operator exception |
(f = foo.bar) | No | Already evaluated, BUT see grouping operator exception |
(foo) | Yes | Grouping operator does not evaluate reference |
(foo.bar) | Yes | Ditto with property reference |
只想說一句:答案都在規(guī)范中,答案都在規(guī)范中,答案都在規(guī)范中 。與其讀微博上某些不靠譜大V的總結(jié),不如去擼一遍規(guī)范。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/90826.html
摘要:正在失業(yè)中的課多周刊第期我們的微信公眾號,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。是一種禍害譯本文淺談了在中關(guān)于的不好之處。淺談超時一運維的排查方式。 正在失業(yè)中的《課多周刊》(第3期) 我們的微信公眾號:fed-talk,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的...
摘要:正在失業(yè)中的課多周刊第期我們的微信公眾號,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。是一種禍害譯本文淺談了在中關(guān)于的不好之處。淺談超時一運維的排查方式。 正在失業(yè)中的《課多周刊》(第3期) 我們的微信公眾號:fed-talk,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的...
摘要:深入系列第六篇,本篇我們追根溯源,從規(guī)范解讀在函數(shù)調(diào)用時到底是如何確定的。因為我們要從規(guī)范開始講起。規(guī)范類型包括和。下一篇文章深入之執(zhí)行上下文深入系列深入系列目錄地址。如果有錯誤或者不嚴謹?shù)牡胤剑垊?wù)必給予指正,十分感謝。 JavaScript深入系列第六篇,本篇我們追根溯源,從 ECMAScript5 規(guī)范解讀 this 在函數(shù)調(diào)用時到底是如何確定的。 前言 在《JavaScript...
摘要:不包括作為其嵌套函數(shù)的被解析的源代碼。作用域鏈當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。棧結(jié)構(gòu)最頂層的執(zhí)行環(huán)境稱為當(dāng)前運行的執(zhí)行環(huán)境,最底層是全局執(zhí)行環(huán)境。無限制函數(shù)上下文。或者拋出異常退出一個執(zhí)行環(huán)境。 前言 其實規(guī)范這東西不是給人看的,它更多的是給語言實現(xiàn)者提供參考。但是當(dāng)碰到問題找不到答案時,規(guī)范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發(fā)和思考,如果只讀一...
閱讀 3686·2021-09-07 10:19
閱讀 3627·2021-09-03 10:42
閱讀 3584·2021-09-03 10:28
閱讀 2548·2019-08-29 14:11
閱讀 809·2019-08-29 13:54
閱讀 1594·2019-08-29 12:14
閱讀 417·2019-08-26 12:12
閱讀 3614·2019-08-26 10:45