摘要:如果形參有設(shè)置默認(rèn)值,第二個就被建立,他針對的是函數(shù)體內(nèi)的聲明我們可以形象的理解為這是一個除了函數(shù)作用域和塊級作用域之外的第三作用域。
開門見山,我們來看看下面這個有趣的例子
?對于上面這種用var的聲明方式,無論x的默認(rèn)值為什么,只要形參中出現(xiàn)了默認(rèn)值,zzz都會被當(dāng)作塊級作用域中的值。
?這是我偶然間遇到的一個問題,起初我認(rèn)為這是chrome的bug,我將我的想法請教了一位朋友,他告訴我說這不是bug,并讓我先看看這篇params default value & params environment & TDZ
?看完后我將我的想法進(jìn)一步告訴了他,我的想法可以用下面這5張圖來概括。
我認(rèn)為這是chrome的bug,如果說是block,那么出了這個塊就不該被訪問到,但是事實是能訪問到
?而且從本身的語法來講,他也不應(yīng)該是block,而是function scope。
?他回答說你沒看懂,并告知我沒看規(guī)范是很難理解,那么沒辦法了,讀讀規(guī)范吧,對規(guī)范已經(jīng)不陌生了,在我的前兩篇文章中,已經(jīng)引用了規(guī)范中的很多內(nèi)容。下面我們先來解釋下規(guī)范中對于這一問題相關(guān)的解釋,然后根據(jù)這些去解釋我們遇到的這一問題。
?注:以下為ES6規(guī)范,ES6規(guī)范,ES6規(guī)范,重要的事情說三遍,不是ES5噢~
8.1 詞法環(huán)境(LexicalEnvironment)?一個詞法環(huán)境是一種規(guī)范的類型,用作定義基于JS代碼的嵌套詞法結(jié)構(gòu)中標(biāo)識符與變量或者函數(shù)間的關(guān)聯(lián)。一個詞法環(huán)境包括一個Environment Records(即作用域記錄,以下我們也簡稱ER)和一個可能為null的指向外部詞法環(huán)境的引用。
?通常一個詞法環(huán)境與JS代碼一些特殊的語法結(jié)構(gòu)想關(guān)聯(lián),如函數(shù)聲明,塊級語句,或者try語句中的catch從句。當(dāng)每次這些代碼被解析的時候,都會創(chuàng)建一個新的詞法環(huán)境。
?一個ER記錄了與它關(guān)聯(lián)的詞法環(huán)境的作用域中的標(biāo)識符綁定。所以稱之為作詞法環(huán)境的ER。
?外部的詞法環(huán)境引用用作模擬邏輯上的詞法環(huán)境嵌套。一個詞法環(huán)境的外部引用也是一個引用,它指向圍繞或者說包括當(dāng)前這個詞法環(huán)境的詞法環(huán)境。當(dāng)然,外部的詞法環(huán)境又有它自己的外部詞法環(huán)境,這就是我們常說的作用域鏈。
?一個詞法環(huán)境可能作為多個內(nèi)部詞法環(huán)境共同的外部詞法環(huán)境。例如,一個函數(shù)聲明中有兩個內(nèi)嵌的函數(shù)聲明。一個語句塊中有兩個內(nèi)嵌的語句塊。
?一個全局環(huán)境是特殊的詞法環(huán)境,它沒有外部詞法環(huán)境,它的外部詞法環(huán)境引用為null。一個全局環(huán)境的ER也許會被用標(biāo)識符綁定進(jìn)行預(yù)填充,包含一些相關(guān)的全局對象,它的屬性提供一些全局環(huán)境下的標(biāo)識符綁定,即內(nèi)置對象,不同的JS宿主環(huán)境,內(nèi)置對象不同。
?這個全局對象就是全局環(huán)境下this的值。當(dāng)JS代碼運(yùn)行的時候,其他的屬性也許會被加入到全局對象中,最初的屬性可能會被修改。
?一個模塊環(huán)境是一個詞法環(huán)境,它包括對于一個模塊頂部聲明的綁定。它也包括對于通過模塊顯式導(dǎo)入(通過import)的模塊的綁定。一個模塊環(huán)境的外部環(huán)境為全局環(huán)境。
?調(diào)用一個函數(shù)的時候,一個函數(shù)環(huán)境也是一個詞法環(huán)境,與函數(shù)對象想對應(yīng)。一個函數(shù)環(huán)境也許會建立一個新的this綁定(比如構(gòu)造函數(shù),對象中的函數(shù)),注意這里的也許二字,因為this只有調(diào)用時才能確定。一個函數(shù)環(huán)境也會捕獲必要的狀態(tài)以支持調(diào)用父級方法。
?詞法環(huán)境和ER值是純粹的規(guī)范,它們不需要對應(yīng)于任何特定的ECMAScript實現(xiàn)。在ECMAScript程序中不可能直接訪問或者操作它們。
8.1.1 Environment Records?在規(guī)范中,有兩種類型的ER,聲明式ER(declarative Environment Records)和對象式ER(object Environment Records)。
?聲明式ER(declarative Environment Records)被用作定義ECMAScript(以下簡稱ES)語言中語法元素的作用,例如函數(shù)聲明,變量聲明,以及catch語句中把綁定的標(biāo)識符與ES語言中的值(Undefined, Null, Boolean, String, Symbol,Number, and Object中的一種,以下簡稱ES合法值)聯(lián)系在一起。
?對象式ER(object Environment Records)被用作定義例如with語句這類把綁定的標(biāo)識符與某些對象聯(lián)系起來的ES元素。
?全局ER(Global Environment Records)和函數(shù)ER(function Environment Records)是專門用作全局腳本聲明和函數(shù)內(nèi)的頂部聲明(也就是我們常說的聲明提升)。
?為了規(guī)范ER的值是Record規(guī)范類型并且能夠存在于簡單的面向?qū)ο髮哟谓Y(jié)構(gòu)中。可以認(rèn)為ER是一個抽象類,他有三個子類-聲明式ER,對象式ER,全局ER。函數(shù)ER和模塊ER(module Environment Records)是聲明式ER的子類。ER這個抽象類包含許多抽象方法(見下表),這些抽象方法在不同的子類中有不同的實現(xiàn)(既然是抽象方法,那么這是必然的)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?表1:ER中的抽象方法
Method | Purpose |
---|---|
HasBinding(N) | 判斷ER中是否綁定有N(即是否有標(biāo)識符N),有返回true,否則返回false |
CreateMutableBinding(N, D) | 在ER中創(chuàng)建一個新的未初始化的且可變的綁定(可以理解為聲明一個變量),N為標(biāo)識符名,D是可選參數(shù),如果為true,這個綁定隨后可能會被刪除。 |
CreateImmutableBinding(N, S) | 在ER中創(chuàng)建一個新的未初始化的且不可變的綁定,N為標(biāo)識符名。如果S為true,無論是否在嚴(yán)格模式下,在它初始化之前嘗試去訪問它的值或者在他初始化后設(shè)置它的值都會拋出異常(就是我們用到的const)。S是可選參數(shù),默認(rèn)為false。 |
InitializeBinding(N,V) | 設(shè)置ER中已經(jīng)存在但是未初始化的綁定的值。N為標(biāo)識符名,V為ES合法值。 |
SetMutableBinding(N,V, S) | 設(shè)置ER中已經(jīng)存在但是未初始化的綁定的值。N為標(biāo)識符名,V為ES合法值。S為一個boolean類型標(biāo)志,如果為true并且無法設(shè)置成你傳入的值,將拋出一個TypeError錯誤。 |
GetBindingValue(N,S) | 返回一個ER中已經(jīng)存在的綁定。N為標(biāo)識符名。S被用作識別原始引用是否在嚴(yán)格模式中或者需要使用嚴(yán)格模式語義。如果S為true且綁定不存在,將拋出一個ReferenceError異常。如果綁定存在但是未初始化,無論S為何值,一個ReferenceError異常將被拋出。 |
DeleteBinding(N) | 從ER中刪除一個綁定。N為標(biāo)識符名,如果N存在,刪除并返回true。如果N存在但是不能被刪除返回false。如果N不存在,返回true。 |
HasThisBinding() | 判斷ER是否綁定了this。(就是我們常用的call和apply)。如果是返回true,否則返回false。 |
HasSuperBinding() | 判斷是否有父類方法綁定。如果是返回true,否則返回false。 |
WithBaseObject () | 如果ER與with語句有關(guān)聯(lián),返回with的對象。否則,返回undefined |
?每個聲明式ER都與一個作用域想關(guān)聯(lián),這個作用域包含var,const,let,class,module,import或者function聲明。一個聲明式ER綁定它的作用域中定義的標(biāo)識符的集合。
有了上面的基本解釋,我們下面來看與提問有關(guān)的地方:
9.2.12 函數(shù)聲明實例化(FunctionDeclarationInstantiation(func, argumentsList))?請記住這里的func和argumentsList,在后面描述過程的時候我們會多次提到
?當(dāng)為了解析一個JS函數(shù)建議執(zhí)行上下文的時候,一個新的函數(shù)ER就被建立,并且綁定這個ER中每個實例化了的形參(這里的實例化應(yīng)該是指在執(zhí)行函數(shù)的時候,形參才能有值,有值之后就代表實例化了)。同時在函數(shù)體中的每個聲明也被實例化了。
?如果函數(shù)的形參不包含任何默認(rèn)值,那么函數(shù)體內(nèi)的聲明將與形參在同一ER中實例化。
?如果形參有設(shè)置默認(rèn)值,第二個ER就被建立,他針對的是函數(shù)體內(nèi)的聲明(我們可以形象的理解為這是一個除了函數(shù)作用域和塊級作用域之外的"第三作用域")。形參和本身的函數(shù)聲明是函數(shù)聲明實例化的一部分。所有其他的聲明在解析函數(shù)體的才會被實例化。
?其實到這里,我們就已經(jīng)能解釋我們提出的問題了,用var聲明的變量在chrome中顯示為Block,并不是代表他為塊級作用域中的值,而僅僅是為了區(qū)分形參的ER和函數(shù)體的ER,形參的ER中的變量只能讀取形參ER中的變量或者函數(shù)外的變量,而函數(shù)體內(nèi)的變量可以讀取函數(shù)體內(nèi),形參,外部的變量。這里摘抄下上面提到的文章中的代碼片段:
let y =1; function foo(x = function(){console.log(y)},y=2) { x(); // 2 var y = 3; // if use let, then throw error: y is already declared, which is much more clear. console.log(y); //3 x(); // 2 } foo(); console.log(y); //1
這便是我們chrome為什么要區(qū)分形參的ER和函數(shù)體的ER的原因,是為了讓我們看得更加清晰。
?問題雖然解決了,但是規(guī)范卻還意猶未盡,有興趣的同學(xué)可以接著往下將這規(guī)則中這一節(jié)的內(nèi)容看完。
?函數(shù)聲明實例化按照如下過程進(jìn)行。其中func為函數(shù)對象,argumentsList為參數(shù)列表。
?1. Let calleeContext 作為運(yùn)行時上下文/運(yùn)行時環(huán)境(Execution Contexts,見下)
Execution Contexts(原文為8.3節(jié)內(nèi)容,但是這里提到了,所以我們在這里就一并解釋了):
一個運(yùn)行時上下文或者說運(yùn)行時環(huán)境是用來跟蹤一個ECMAScript實現(xiàn)(注意ES實現(xiàn)不止JS一種)的代碼的運(yùn)行時解析。在運(yùn)行時的任意時間點,最多只存在一個運(yùn)行時上下文,即當(dāng)前執(zhí)行的代碼。
一個棧被用作跟蹤運(yùn)行時環(huán)境,運(yùn)行時環(huán)境總是指向棧頂?shù)脑兀ㄒ簿褪俏覀兂Uf的調(diào)用棧,chrome調(diào)試時的call stack)。無論何時,只要運(yùn)行時環(huán)境從當(dāng)前運(yùn)行的代碼轉(zhuǎn)移到非當(dāng)前運(yùn)行時環(huán)境的代碼,就會創(chuàng)建一個新的運(yùn)行時環(huán)境,并將這個新的運(yùn)行時環(huán)境push到棧頂,成為當(dāng)前的運(yùn)行時環(huán)境。
為了跟蹤代碼的執(zhí)行過程,一個運(yùn)行時環(huán)境包含實現(xiàn)具體的狀態(tài)是有必要的。每一個運(yùn)行時環(huán)境都至少有下表列出的這幾種元素。
Component | Purpose |
---|---|
code evaluation state | 包含與運(yùn)行時環(huán)境相關(guān)的代碼所需的任何狀態(tài),如執(zhí)行中,暫停,繼續(xù)解析 |
Function | 如果運(yùn)行時環(huán)境正在解析一個函數(shù)對象,那么這個值就為那個函數(shù)對象。如果正在解析一個腳本(script)或者模塊(module),那么這個值為null |
Realm(域) | 來自相關(guān)代碼可以訪問的ECMAScript resources的域。注:ECMAScript resources包含客戶端ECMAScript,ECMAScript核心標(biāo)準(zhǔn)庫,擴(kuò)展自ECMAScript核心標(biāo)準(zhǔn)庫的服務(wù)端ECMAScript。域包括全局對象和內(nèi)置對象 |
運(yùn)行時環(huán)境的代碼解析可能會被各種各樣的情況打斷而導(dǎo)致暫停或者說掛起。一旦運(yùn)行時環(huán)境切換到另一個不同的運(yùn)行時環(huán)境,那么這個不同的環(huán)境就可能成為當(dāng)前運(yùn)行時環(huán)境,并開始解析代碼。一段時間過后,一個暫停的執(zhí)行環(huán)境也許會成為運(yùn)行時環(huán)境并且從之前的暫停點繼續(xù)解析代碼。運(yùn)行時環(huán)境的這種來回切換的狀態(tài)是通過類棧結(jié)構(gòu)來過渡的。然而,一些ES特性需要非棧的過渡。
運(yùn)行時環(huán)境的Realm的值也被稱作當(dāng)前域。運(yùn)行時環(huán)境的Function的值也被成為活動函數(shù)對象。
ECMAScript的運(yùn)行時環(huán)境有額外的state元素(見下表)
Component | Purpose |
---|---|
LexicalEnvironment | 標(biāo)記用作解析當(dāng)前運(yùn)行時環(huán)境中代碼里的標(biāo)識符引用的詞法環(huán)境 |
VariableEnvironment | 標(biāo)記在當(dāng)前運(yùn)行時環(huán)境中詞法環(huán)境的ER包括var聲明創(chuàng)建的綁定的詞法環(huán)境 |
上表中的詞法環(huán)境(LexicalEnvironment)和變量環(huán)境(VariableEnvironment),在一個運(yùn)行時環(huán)境中總是表現(xiàn)為詞法環(huán)境。當(dāng)一個運(yùn)行時環(huán)境被創(chuàng)建的時候,它的詞法環(huán)境和變量環(huán)境初始化為相同的值。
可以參考下stackoverflow上的解釋1以及stackoverflow上的解釋2:
// VariableEnvironment (global) = { __outer__: null } // LexicalEnvironment = VariableEnvironment (global) (function foo() { // VariableEnvironment (A) = { x: undefined, __outer__: global } // LexicalEnvironment = VariableEnvironment (A) var x; (function bar(){ // VariableEnvironment (B) = { y: undefined, __outer__: A } // LexicalEnvironment = VariableEnvironment (B) var y; x = 2; // VariableEnvironment (A) = { x: 2, __outer__: global } // LexicalEnvironment is still the same as VariableEnvironment (B) })(); })();
對于構(gòu)造器的運(yùn)行時上下文,有額外的的state元素(見下表)
Component | Purpose |
---|---|
Generator | 當(dāng)前運(yùn)行時環(huán)境正在解析的構(gòu)造器對象 |
在大多數(shù)情況下,只有當(dāng)前運(yùn)行時環(huán)境(即運(yùn)行時環(huán)境棧的棧頂元素)直接被規(guī)范中的算法操作。
?2. Let env 作為calleeContext(當(dāng)前的運(yùn)行時上下文,也就是運(yùn)行時上下文的棧頂元素)的詞法環(huán)境(LexicalEnvironment)
?3. Let envRec 作為env的ER
?4. Let code 等于[[ECMAScriptCode]]這個func的內(nèi)嵌屬性的值(內(nèi)嵌屬性(兩個中括號包裹的屬性)并不是ES的一部分,由ES的具體實現(xiàn)來定義,它們純粹是為了展示,更重要的一點,它們具有多態(tài)性。下面再看到中括號就不再解釋內(nèi)嵌屬性了)
[[ECMAScriptCode]]:類型為Node。值為源代碼文件解析后的函數(shù)體,即函數(shù)對象有一個屬性[[ECMAScriptCode]]可以指向自身的函數(shù)體。
?5. Let strict 等于[[Strict]]的值
[[Strict]]: 類型為boolean。如果為true代表這是一個嚴(yán)格模式下的函數(shù)
?6. Let formals 等于[[FormalParameters]]的值
[[FormalParameters]]:類型為Node。指向函數(shù)的形參列表。
?7. Let parameterNames 等于formals的BoundNames,即如果形參為x, y那么parameterNames為["x", "y"]
?8. 如果parameterNames里有重復(fù)的,將hasDuplicates置為true,否則置為false
?9. Let simpleParameterList等于formals的IsSimpleParameterList
IsSimpleParameterList:如果形參為空或者只是普通的標(biāo)識符則返回true,其他的如形參為rest參數(shù)(...x),普通參數(shù)加rest參數(shù)(x, ...y),參數(shù)有默認(rèn)值,參數(shù)有解構(gòu)賦值等等,都返回false
?10. Let hasParameterExpressions等于formals的ContainsExpression的值
ContainsExpression:形參含有默認(rèn)值則為true,否則為false
?11. Let varNames等于函數(shù)的VarDeclaredNames(只包含函數(shù)體里的變量,不包含形參)的值
?12. Let varDeclarations等于函數(shù)的VarScopedDeclarations的值
VarDeclaredNames與VarScopedDeclarations的區(qū)別:VarDeclaredNames是一個類型為Name的Set(Name只包含標(biāo)識符名,作用域等等)。而VarScopedDeclarations是一個類型為StatementListItem的List(StatementListItem代表的是語句元素,ES一共有14種語句),就這里的語句而言,指的是VariableStatement,對于我們解析而已,是把語句(也就是Statement)當(dāng)作一個語法樹節(jié)點
?13. Let lexicalNames等于函數(shù)的LexicallyDeclaredNames(不包含var和function聲明)
?14. Let functionNames等于一個空的List
?15. Let functionsToInitialize等于一個空的List
?16. 對于變量varDeclarations其中的每個元素d,如果d既不是VariableDeclaration也不是ForBinding(for in或者for of結(jié)構(gòu)里面進(jìn)行聲明)。那么:
進(jìn)行Assert(斷言),判斷d是否是函數(shù)聲明或者構(gòu)造器聲明
Let fn等于d的BoundNames
如果fn不是functionNames里的元素,那么
將fn用頭插法插入functionNames
注意如果fn有多次重復(fù)出現(xiàn),則以最后一次為準(zhǔn)
將d用頭插法插入functionsToInitialize
?17. 聲明一個argumentsObjectNeeded,賦值為true
?18. 如果func的內(nèi)嵌屬性[[ThisMode]]的值為lexical,那么
將argumentsObjectNeeded賦值為false(注意箭頭函數(shù)沒有arguments對象)
[[ThisMode]]:作用是定義在函數(shù)形參和函數(shù)體內(nèi)如何解析this引用。值為lexical代表this指向詞法閉包的this值(詞法閉包就是我們常說的閉包,具體可以看我的上一篇文章),strict代表this值完全由函數(shù)調(diào)用提供。global代表this值為undefined
?19. 否則(接上)如果arguments是parameterNames(在第7步聲明)的一個元素(也就是形參里面我們使用了arguments作為標(biāo)識符), 那么將argumentsObjectNeeded賦值為false
?20. 否則(接上)如果hasParameterExpressions(在第10步聲明)等于false,那么
如果arguments是functionNames(在第14步聲明)的一個元素,或者是lexicalNames(在第13步聲明)的一個元素,那么將argumentsObjectNeeded賦值為false
?21. 對于parameterNames(在第7步聲明)中每個元素paramName:
a.Let alreadyDeclared等于envRec.HasBinding(paramName)的值(即判斷當(dāng)前環(huán)境中是否綁定過paramName)
b.注意:早期的錯誤檢查確保了多個重復(fù)的形參參數(shù)數(shù)名只可能出現(xiàn)在形參沒有默認(rèn)值和rest參數(shù)的非嚴(yán)格模式下的函數(shù)中:
1. function func(x, x = 2) {} // 報錯 2. function func(x, ...x) {} // 報錯 3. function func(x, x) {} // 不報錯 4. "use strict"; function func(x, x) {} // 報錯
c.如果alreadyDeclared等于false,那么:
c.1 Let status等于envRec.CreateMutableBinding(paramName)(表1中有這個方法)的值(即將聲明的參數(shù)綁定到函數(shù)的作用域中)
c.2 如果hasDuplicates(在第8步聲明)等于true,那么:
Let status等于envRec.InitializeBinding(paramName, undefined)(表1中有這個方法)的值
c.3 斷言:在上面兩步操作中(c.1和c.2),status不可能是一個 abrupt completion(可以簡單的理解為break,continue,return和throw操作)
?22. 如果argumentsObjectNeeded(第17-20步改變)等于true,那么:
a.如果strict(第5步聲明)等于true或者simpleParameterList(第9步聲明)等于false,那么:
a.1 Let ao等于CreateUnmappedArgumentsObject(argumentsList)的值
b.否則(接上面的a步驟):
b.1 注意:mapped argument(與上面的Unmapped對應(yīng))對象僅在非嚴(yán)格模式下且形參沒有rest參數(shù),默認(rèn)值,解構(gòu)賦值的函數(shù)中提供。(滿足這三個條件其實simpleParameterList就為true了)
b.2 Let ao等于CreateMappedArgumentsObject(func, formals, argumentsList, env)的值
注:CreateUnmappedArgumentsObject和CreateMappedArgumentsObject簡單來說就是根據(jù)參數(shù)形式的不同創(chuàng)建不同的arguments`對象
c.ReturnIfAbrupt(ao)
d.如果strict等于true,那么:
d.1 Let status等于envRec.CreateImmutableBinding("arguments")(表1中有介紹)的值
e.否則(接上面的c步驟),Let status等于envRec.CreateMutableBinding("arguments")(表1中有介紹)的值
f.斷言:status不可能是一個 abrupt completion
g.執(zhí)行envRec.InitializeBinding("arguments", ao)(表1中有介紹)
h.向parameterNames(第7步中聲明)中appendarguments
?23. Let iteratorRecord等于Record {[[iterator]]: CreateListIterator(argumentsList), [[done]]: false}(即建立一個內(nèi)置迭代器屬性,讓arguments變成可迭代的)
?24. 如果hasDuplicates(第8步中聲明)等于true,那么:
a.Let formalStatus等于formals去調(diào)用IteratorBindingInitialization,用iteratorRecord和undefined作為參數(shù)的返回值
?25. 否則(接上面的24步驟):
a.Let formalStatus等于formals去調(diào)用IteratorBindingInitialization,用iteratorRecord和env作為參數(shù)的返回值(可以看到只有最后一個參數(shù)和24步不一樣)
IteratorBindingInitialization(iteratorRecord,environment):當(dāng)environment為undefined的時候,這意味著應(yīng)該用一個PutValue(即將一個值放入一個對象)操作去初始化值。這是針對非嚴(yán)格模式情況下的一個考慮(因為嚴(yán)格模式下在24步應(yīng)該是false)。在這種情況下,形參被預(yù)初始化,目的是解決多個參數(shù)名相同的問題。
?26. ReturnIfAbrupt(formalStatus)
?27. 如果hasParameterExpressions(第10步聲明)等于false,那么:
a.注意:對于形參和聲明提取的變量,僅僅只需要一個單一的詞法環(huán)境
b.Let instantiatedVarNames等于parameterNames的一個副本
c.對于varNames(第11步中聲明)的每個元素n:
c.1 如果n不是instantiatedVarNames里的元素,那么:
c.1.1 appendn到instantiatedVarNames中
c.1.2 Let status等于envRec.CreateMutableBinding(n)
c.1.3 斷言:status不可能是一個 abrupt completion
c.1.4 執(zhí)行envRec.InitializeBinding(n, undefined)
d.Let varEnv等于env
e.Let varEnvRec等于envRec
?28. 否則(接上面的27步驟):
a.注意:一個多帶帶的ER是有必要的,目的是確保形參中的表達(dá)式創(chuàng)建的閉包對函數(shù)體的變量不具有可訪問性(即我們提到的"第三作用域")
b.Let varEnv等于NewDeclarativeEnvironment(env)的值(即創(chuàng)建一個新的詞法環(huán)境,它的ER里沒有任何綁定,這個ER的外部或者說父級詞法環(huán)境在這里就是env)
c.Let varEnvRec等于varEnv的ER
d.將calleeContext(第1步中聲明)的VariableEnvironment設(shè)為varEnv
e.Let instantiatedVarNames等于一個空的List
f.對于varNames中的每個元素n:
f.a 如果n不是instantiatedVarNames中的元素,那么:
f.a.1 appendn到instantiatedVarNames中
f.a.2 Let status等于varEnvRec.CreateMutableBinding(n)(varEnvRec在27.e步或者28.c步中聲明,CreateMutableBinding參考表1)的值
f.a.3 斷言:status不可能是一個 abrupt completion
f.a.4 如果n不是parameterNames(第7步中聲明)中的元素,或者n是functionNames(第14步中聲明)中的元素,Let initialValue等于undefined
f.a.5 否則(接上面的f.a.4步驟):
f.a.5.1 Let initialValue等于envRec.GetBindingValue(n, false)(envRec在第3步中聲明,GetBindingValue參考表1)
f.a.5.2 ReturnIfAbrupt(initialValue)
f.a.6 執(zhí)行varEnvRec.InitializeBinding(n, initialValue)(varEnvRec在27.e步或者28.c步中聲明,InitializeBinding參考表1)
f.a.7 注意:形參中相同標(biāo)識符的變量,當(dāng)它們對應(yīng)的形參初始化的時候,它們的值是一樣的。(意思就是比如function func(x, x) {},調(diào)用時func(111),那么當(dāng)?shù)诙€x初始化的時候,第一個x也就變成undefined了,因為它們的值要保持一致,所以最后x為undefined)
?29. 注意:附錄B.3.3在這一點有額外的步驟(有興趣可以去看看,主要是介紹了瀏覽器宿主環(huán)境對于塊級函數(shù)聲明的解析和規(guī)范的差異)
?30. 如果strict等于false,那么:
a.Let lexEnv等于NewDeclarativeEnvironment(varEnv)的值(即創(chuàng)建一個新的詞法環(huán)境,它的ER里沒有任何綁定,這個ER的外部或者說父級詞法環(huán)境在這里就是varEnv)
b.注意:非嚴(yán)格模式下的函數(shù)對于頂層聲明采用的是一個多帶帶的詞法作用域,因此直接調(diào)用eval(var a = eval; a(xx)這叫間接調(diào)用)能夠?qū)δ切┮呀?jīng)聲明過的會導(dǎo)致沖突。在嚴(yán)格模式下這是不需要的,因為嚴(yán)格模式下的eval總是把聲明放到一個新的ER中
function qq(){var a = 1; eval("var a = 55;"); console.log(a);} // 輸出55 "use strict"; function qq(){var a = 1; eval("var a = 55;"); console.log(a);} // 輸出1
?31.否則(接上面的30步驟),Let lexEnv 等于varEnv(在27.d或者28.b中聲明)
?32. Let lexEnvRec等于lexEnv的ER
?33. 將calleeContext(第1步中聲明)的ER設(shè)置為lexEnv
?34. Let lexDeclarations等于函數(shù)的LexicallyScopedDeclarations
?35. 對于lexDeclarations中的每個元素d:
a.注意:一個詞法聲明的標(biāo)識符不能和函數(shù),產(chǎn)生器函數(shù),形參或者其他變量名相同。詞法聲明的標(biāo)識符只會在這里實例化而不是初始化。
b.對于BoundNames中的每個元素dn:
b.1 如果d是常量聲明,那么:
b.1.1 Let status等于lexEnvRec.CreateImmutableBinding(dn, true)
b.1.2 Let status等于lexEnvRec.CreateMutableBinding(dn, false)
c.斷言:status不可能是一個 abrupt completion
?36. 對于functionsToInitialize中的每個解析過的語法短語(這里的短語指的是編譯原理里的短語)f:
a.Let fn作為f的BoundNames的唯一元素
b.Let fo等于執(zhí)行InstantiateFunctionObject(f, lexEnv)的結(jié)果
InstantiateFunctionObject(f, lexEnv):
c.Let status等于varEnvRec.SetMutableBinding(fn, fo, false)
d.斷言:status不可能是一個 abrupt completion
?37. 返回NormalCompletion(empty)(即返回 Completion{[[type]]: normal, [[value]]: empty, [[target]]:empty})
注意:附錄B.3.3關(guān)于上面的算法提供了一種擴(kuò)展,這種擴(kuò)展對于瀏覽器在ES2015之前實現(xiàn)ECMAScript向后兼容是有必要的。(也就是我們常說的ployfill)
注意:形參的Initializers(即默認(rèn)值)也許包含eval表達(dá)式。任何在這個eval里面聲明的變量只能在這個eval內(nèi)才能訪問。
寫在結(jié)尾?在探索和翻譯的過程中,確實是遇到了一些困難,包括到現(xiàn)在也還有一些困惑仍未解決。經(jīng)過這次探索,想到一位大牛曾回答過"作為程序員,哪些網(wǎng)站是必須了解的"的問題,他的回答是"除了github和stackoverflow,應(yīng)該沒有其他是必須的",算是比較深刻的體會到了一這點,很多東西google和wiki都是找不到的,只能求助于so,沒有的話還需要自己提問和gh上提issue。
?一條評論可能又會提到其他地方,其他地方又會鏈接到不同的人,不同的技術(shù),不同的想法。這樣都去瀏覽或者了解一番,便能開闊眼界,從一個單一知識點入手,不單單是解決這一個問題。或許我們還能學(xué)到很多新的知識,方式,想法,了解一些新的工具,認(rèn)識一些有趣的人。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/80958.html
摘要:中函數(shù)是一等公民。小明小明調(diào)用函數(shù)時,傳遞給函數(shù)的值被稱為函數(shù)的實參值傳遞,對應(yīng)位置的函數(shù)參數(shù)名叫作形參。所以不推薦使用構(gòu)造函數(shù)創(chuàng)建函數(shù)因為它需要的函數(shù)體作為字符串可能會阻止一些引擎優(yōu)化也會引起瀏覽器資源回收等問題。 函數(shù) 之前幾節(jié)中圍繞著函數(shù)梳理了 this、原型鏈、作用域鏈、閉包等內(nèi)容,這一節(jié)梳理一下函數(shù)本身的一些特點。 javascript 中函數(shù)是一等公民。 并且函數(shù)也是對象,...
摘要:函數(shù)的形參和函數(shù)體就是兩個不同的作用域。這里只聲明了還是形參這里改變了形參的值,所以返回是這題還有一個坑點,我拿到里面去轉(zhuǎn)一下得到的結(jié)果是這里結(jié)果是這種神奇的代碼還是盡量不要寫呀如果有理解錯誤的地方,歡迎指正 把話說完:90%面試官都不會問的題,因為面試官也不一定懂。 直接來看一看今天要說的題目: // 問題:foo.x的值是什么?bar.x? var foo = { n: 1 }; ...
摘要:在中,每一個節(jié)點被稱為回流重繪回流中部分全部元素的規(guī)模尺寸布局等改變而需要重新構(gòu)建頁面。而就是通過調(diào)用構(gòu)造函數(shù)創(chuàng)建的對象實例的原型對象原型所指的就是一個對象,實例繼承對象的屬性。 亂序 不間斷更新 絕大多數(shù)寫的比較淺顯 看個樂子 display:none 和visibility:hidden的區(qū)別 display:none徹底消失將會隱藏它以及所有的后代元素占據(jù)的空間消失,瀏覽器不會...
摘要:在中,每一個節(jié)點被稱為回流重繪回流中部分全部元素的規(guī)模尺寸布局等改變而需要重新構(gòu)建頁面。而就是通過調(diào)用構(gòu)造函數(shù)創(chuàng)建的對象實例的原型對象原型所指的就是一個對象,實例繼承對象的屬性。 亂序 不間斷更新 絕大多數(shù)寫的比較淺顯 看個樂子 display:none 和visibility:hidden的區(qū)別 display:none徹底消失將會隱藏它以及所有的后代元素占據(jù)的空間消失,瀏覽器不會...
閱讀 829·2019-08-30 14:05
閱讀 1720·2019-08-30 11:08
閱讀 3220·2019-08-29 15:41
閱讀 3596·2019-08-23 18:31
閱讀 1518·2019-08-23 18:29
閱讀 551·2019-08-23 14:51
閱讀 2110·2019-08-23 13:53
閱讀 2130·2019-08-23 13:02