<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 前言 在第12章關于變量對象的描述中,我們已經知道一個執行上下文 的數據(變量、函數聲明和函數的形參)作為屬性存儲在變量對象中。 同時我們也知道變量對象在每次進入上下文時創建,并填入初始值,值的更新出現在代碼執行階段。 這一章專門討論與執行上下文直接相關的更多細節,這次我們將提及一個議題——作用域鏈。 > 英文原文:http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/ > 中文參考:http://www.denisdeng.com/?p=908 > 本文絕大部分內容來自上述地址,僅做少許修改,感謝作者 ## 定義 如果要簡要的描述并展示其重點,那么作用域鏈大多數與內部函數相關。 我們知道,ECMAScript 允許創建內部函數,我們甚至能從父函數中返回這些函數。 ~~~ var x = 10; function foo() { var y = 20; function bar() { alert(x + y); } return bar; } foo()(); // 30 ~~~ 這樣,很明顯每個上下文擁有自己的變量對象:對于全局上下文,它是全局對象自身;對于函數,它是活動對象。 作用域鏈正是內部上下文所有變量對象(包括父變量對象)的列表。此鏈用來變量查詢。即在上面的例子中,“bar”上下文的作用域鏈包括AO(bar)、AO(foo)和VO(global)。 但是,讓我們仔細研究這個問題。 讓我們從定義開始,并進深一步的討論示例。 作用域鏈與一個執行上下文相關,變量對象的鏈用于在標識符解析中變量查找。 函數上下文的作用域鏈在函數調用時創建的,包含活動對象和這個函數內部的[[scope]]屬性。下面我們將更詳細的討論一個函數的[[scope]]屬性。 在上下文中示意如下: ~~~ activeExecutionContext = { VO: {...}, // or AO this: thisValue, Scope: [ // Scope chain // 所有變量對象的列表 // for identifiers lookup ] }; ~~~ 其scope定義如下: ~~~ Scope = AO + [[Scope]] ~~~ 這種聯合和標識符解析過程,我們將在下面討論,這與函數的生命周期相關。 ## 函數的生命周期 函數的的生命周期分為創建和激活階段(調用時),讓我們詳細研究它。 ### 函數創建 眾所周知,在進入上下文時函數聲明放到變量/活動(VO/AO)對象中。讓我們看看在全局上下文中的變量和函數聲明(這里變量對象是全局對象自身,我們還記得,是吧?) ~~~ var x = 10; function foo() { var y = 20; alert(x + y); } foo(); // 30 ~~~ 在函數激活時,我們得到正確的(預期的)結果--30。但是,有一個很重要的特點。 此前,我們僅僅談到有關當前上下文的變量對象。這里,我們看到變量“y”在函數“foo”中定義(意味著它在foo上下文的AO中),但是變量“x”并未在“foo”上下文中定義,相應地,它也不會添加到“foo”的AO中。乍一看,變量“x”相對于函數“foo”根本就不存在;但正如我們在下面看到的——也僅僅是“一瞥”,我們發現,“foo”上下文的活動對象中僅包含一個屬性--“y”。 ~~~ fooContext.AO = { y: undefined // undefined – 進入上下文的時候是20 – at activation }; ~~~ 函數“foo”如何訪問到變量“x”?理論上函數應該能訪問一個更高一層上下文的變量對象。實際上它正是這樣,這種機制是通過函數內部的[[scope]]屬性來實現的。 [[scope]]是所有父變量對象的層級鏈,處于當前函數上下文之上,在函數創建時存于其中。 注意這重要的一點--[[scope]]在函數創建時被存儲--靜態(不變的),永遠永遠,直至函數銷毀。即:函數可以永不調用,但[[scope]]屬性已經寫入,并存儲在函數對象中。 另外一個需要考慮的是--與作用域鏈對比,[[scope]]是函數的一個屬性而不是上下文。考慮到上面的例子,函數“foo”的[[scope]]如下: ~~~ foo.[[Scope]] = [ globalContext.VO // === Global ]; ~~~ 舉例來說,我們用通常的ECMAScript 數組展現作用域和[[scope]]。 繼續,我們知道在函數調用時進入上下文,這時候活動對象被創建,this和作用域(作用域鏈)被確定。讓我們詳細考慮這一時刻。 ### 函數激活 正如在定義中說到的,進入上下文創建AO/VO之后,上下文的Scope屬性(變量查找的一個作用域鏈)作如下定義: ~~~ Scope = AO|VO + [[Scope]] ~~~ 上面代碼的意思是:活動對象是作用域數組的第一個對象,即添加到作用域的前端。 ~~~ Scope = [AO].concat([[Scope]]); ~~~ 這個特點對于標示符解析的處理來說很重要。 標示符解析是一個處理過程,用來確定一個變量(或函數聲明)屬于哪個變量對象。 這個算法的返回值中,我們總有一個引用類型,它的base組件是相應的變量對象(或若未找到則為null),屬性名組件是向上查找的標示符的名稱。引用類型的詳細信息在第13章.this中已討論。 標識符解析過程包含與變量名對應屬性的查找,即作用域中變量對象的連續查找,從最深的上下文開始,繞過作用域鏈直到最上層。 這樣一來,在向上查找中,一個上下文中的局部變量較之于父作用域的變量擁有較高的優先級。萬一兩個變量有相同的名稱但來自不同的作用域,那么第一個被發現的是在最深作用域中。 我們用一個稍微復雜的例子描述上面講到的這些。 ~~~ var x = 10; function foo() { var y = 20; function bar() { var z = 30; alert(x + y + z); } bar(); } foo(); // 60 ~~~ 對此,我們有如下的變量/活動對象,函數的的[[scope]]屬性以及上下文的作用域鏈: 全局上下文的變量對象是: ~~~ globalContext.VO === Global = { x: 10 foo: function> }; ~~~ 在“foo”創建時,“foo”的[[scope]]屬性是: ~~~ foo.[[Scope]] = [ globalContext.VO ]; ~~~ 在“foo”激活時(進入上下文),“foo”上下文的活動對象是: ~~~ fooContext.AO = { y: 20, bar: function> }; ~~~ “foo”上下文的作用域鏈為: ~~~ fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.: fooContext.Scope = [ fooContext.AO, globalContext.VO ]; ~~~ 內部函數“bar”創建時,其[[scope]]為: ~~~ bar.[[Scope]] = [ fooContext.AO, globalContext.VO ]; ~~~ 在“bar”激活時,“bar”上下文的活動對象為: ~~~ barContext.AO = { z: 30 }; ~~~ “bar”上下文的作用域鏈為: ~~~ barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.: barContext.Scope = [ barContext.AO, fooContext.AO, globalContext.VO ]; ~~~ 對“x”、“y”、“z”的標識符解析如下: ~~~ - "x" -- barContext.AO // not found -- fooContext.AO // not found -- globalContext.VO // found - 10 - "y" -- barContext.AO // not found -- fooContext.AO // found - 20 - "z" -- barContext.AO // found - 30 ~~~ ## 作用域特征 讓我們看看與作用域鏈和函數[[scope]]屬性相關的一些重要特征。 ### 閉包 在ECMAScript中,閉包與函數的[[scope]]直接相關,正如我們提到的那樣,[[scope]]在函數創建時被存儲,與函數共存亡。實際上,閉包是函數代碼和其[[scope]]的結合。因此,作為其對象之一,[[Scope]]包括在函數內創建的詞法作用域(父變量對象)。當函數進一步激活時,在變量對象的這個詞法鏈(靜態的存儲于創建時)中,來自較高作用域的變量將被搜尋。 例如: ~~~ var x = 10; function foo() { alert(x); } (function () { var x = 20; foo(); // 10, but not 20 })(); ~~~ 我們再次看到,在標識符解析過程中,使用函數創建時定義的詞法作用域--變量解析為10,而不是30。此外,這個例子也清晰的表明,一個函數(這個例子中為從函數“foo”返回的匿名函數)的[[scope]]持續存在,即使是在函數創建的作用域已經完成之后。 關于ECMAScript中閉包的理論和其執行機制的更多細節,閱讀16章閉包。 ### 通過構造函數創建的函數的[[scope]] 在上面的例子中,我們看到,在函數創建時獲得函數的[[scope]]屬性,通過該屬性訪問到所有父上下文的變量。但是,這個規則有一個重要的例外,它涉及到通過函數構造函數創建的函數。 ~~~ var x = 10; function foo() { var y = 20; function barFD() { // 函數聲明 alert(x); alert(y); } var barFE = function () { // 函數表達式 alert(x); alert(y); }; var barFn = Function('alert(x); alert(y);'); barFD(); // 10, 20 barFE(); // 10, 20 barFn(); // 10, "y" is not defined } foo(); ~~~ 我們看到,通過函數構造函數(Function constructor)創建的函數“bar”,是不能訪問變量“y”的。但這并不意味著函數“barFn”沒有[[scope]]屬性(否則它不能訪問到變量“x”)。問題在于通過函構造函數創建的函數的[[scope]]屬性總是唯一的全局對象。考慮到這一點,如通過這種函數創建除全局之外的最上層的上下文閉包是不可能的。 ### 二維作用域鏈查找 在作用域鏈中查找最重要的一點是變量對象的屬性(如果有的話)須考慮其中--源于ECMAScript 的原型特性。如果一個屬性在對象中沒有直接找到,查詢將在原型鏈中繼續。即常說的二維鏈查找。(1)作用域鏈環節;(2)每個作用域鏈--深入到原型鏈環節。如果在Object.prototype 中定義了屬性,我們能看到這種效果。 ~~~ function foo() { alert(x); } Object.prototype.x = 10; foo(); // 10 ~~~ 活動對象沒有原型,我們可以在下面的例子中看到: ~~~ function foo() { var x = 20; function bar() { alert(x); } bar(); } Object.prototype.x = 10; foo(); // 20 ~~~ 如果函數“bar”上下文的激活對象有一個原型,那么“x”將在Object.prototype 中被解析,因為它在AO中不被直接解析。但在上面的第一個例子中,在標識符解析中,我們到達全局對象(在一些執行中并不全是這樣),它從Object.prototype繼承而來,響應地,“x”解析為10。 同樣的情況出現在一些版本的SpiderMokey 的命名函數表達式(縮寫為NFE)中,在那里特定的對象存儲從Object.prototype繼承而來的函數表達式的可選名稱,在Blackberry中的一些版本中,執行時激活對象從Object.prototype繼承。但是,關于該特色的更多細節在第15章函數討論。 ### 全局和eval上下文中的作用域鏈 這里不一定很有趣,但必須要提示一下。全局上下文的作用域鏈僅包含全局對象。代碼eval的上下文與當前的調用上下文(calling context)擁有同樣的作用域鏈。 ~~~ globalContext.Scope = [ Global ]; evalContext.Scope === callingContext.Scope; ~~~ ### 代碼執行時對作用域鏈的影響 在ECMAScript 中,在代碼執行階段有兩個聲明能修改作用域鏈。這就是with聲明和catch語句。它們添加到作用域鏈的最前端,對象須在這些聲明中出現的標識符中查找。如果發生其中的一個,作用域鏈簡要的作如下修改: ~~~ Scope = withObject|catchObject + AO|VO + [[Scope]] ~~~ 在這個例子中添加對象,對象是它的參數(這樣,沒有前綴,這個對象的屬性變得可以訪問)。 ~~~ var foo = {x: 10, y: 20}; with (foo) { alert(x); // 10 alert(y); // 20 } ~~~ 作用域鏈修改成這樣: ~~~ Scope = foo + AO|VO + [[Scope]] ~~~ 我們再次看到,通過with語句,對象中標識符的解析添加到作用域鏈的最前端: ~~~ var x = 10, y = 10; with ({x: 20}) { var x = 30, y = 30; alert(x); // 30 alert(y); // 30 } alert(x); // 10 alert(y); // 30 ~~~ 在進入上下文時發生了什么?標識符“x”和“y”已被添加到變量對象中。此外,在代碼運行階段作如下修改: 1. x = 10, y = 10; 2. 對象{x:20}添加到作用域的前端; 3. 在with內部,遇到了var聲明,當然什么也沒創建,因為在進入上下文時,所有變量已被解析添加; 4. 在第二步中,僅修改變量“x”,實際上對象中的“x”現在被解析,并添加到作用域鏈的最前端,“x”為20,變為30; 5. 同樣也有變量對象“y”的修改,被解析后其值也相應的由10變為30; 6. 此外,在with聲明完成后,它的特定對象從作用域鏈中移除(已改變的變量“x”--30也從那個對象中移除),即作用域鏈的結構恢復到with得到加強以前的狀態。 7. 在最后兩個alert中,當前變量對象的“x”保持同一,“y”的值現在等于30,在with聲明運行中已發生改變。 同樣,catch語句的異常參數變得可以訪問,它創建了只有一個屬性的新對象--異常參數名。圖示看起來像這樣: ~~~ try { ... } catch (ex) { alert(ex); } ~~~ 作用域鏈修改為: ~~~ var catchObject = { ex: }; Scope = catchObject + AO|VO + [[Scope]] ~~~ 在catch語句完成運行之后,作用域鏈恢復到以前的狀態。 ## 結論 在這個階段,我們幾乎考慮了與執行上下文相關的所有常用概念,以及與它們相關的細節。按照計劃--函數對象的詳細分析:函數類型(函數聲明,函數表達式)和閉包。順便說一下,在這篇文章中,閉包直接與[[scope]]屬性相關,但是,關于它將在合適的篇章中討論。我很樂意在評論中回答你的問題。 ## 其它參考 * 8.6.2 – [[[Scope]]](http://bclary.com/2004/11/07/#a-8.6.2) * 10.1.4 – [Scope Chain and Identifier Resolution](http://bclary.com/2004/11/07/#a-10.1.4)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看