<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 介紹 JavaScript編程的時候總避免不了聲明函數和變量,以成功構建我們的系統,但是解釋器是如何并且在什么地方去查找這些函數和變量呢?我們引用這些對象的時候究竟發生了什么? > 原始發布:Dmitry A. Soshnikov > 發布時間:2009-06-27 > 俄文地址:http://dmitrysoshnikov.com/ecmascript/ru-chapter-2-variable-object/ > 英文翻譯:Dmitry A. Soshnikov > 發布時間:2010-03-15 > 英文地址:http://dmitrysoshnikov.com/ecmascript/chapter-2-variable-object/ > 部分難以翻譯的句子參考了[justinw的中文翻譯](http://www.cnblogs.com/justinw/archive/2010/04/23/1718733.html) 大多數ECMAScript程序員應該都知道變量與執行上下文有密切關系: ~~~ var a = 10; // 全局上下文中的變量 (function () { var b = 20; // function上下文中的局部變量 })(); alert(a); // 10 alert(b); // 全局變量 "b" 沒有聲明 ~~~ 并且,很多程序員也都知道,當前ECMAScript規范指出獨立作用域只能通過“函數(function)”代碼類型的執行上下文創建。也就是說,相對于C/C++來說,ECMAScript里的for循環并不能創建一個局部的上下文。 ~~~ for (var k in {a: 1, b: 2}) { alert(k); } alert(k); // 盡管循環已經結束但變量k依然在當前作用域 ~~~ 我們來看看一下,我們聲明數據的時候到底都發現了什么細節。 ## 數據聲明 如果變量與執行上下文相關,那變量自己應該知道它的數據存儲在哪里,并且知道如何訪問。這種機制稱為變量對象(variable object)。 變量對象(縮寫為VO)是一個與執行上下文相關的特殊對象,它存儲著在上下文中聲明的以下內容: > 變量 (var, 變量聲明); > 函數聲明 (FunctionDeclaration, 縮寫為FD); > 函數的形參 舉例來說,我們可以用普通的ECMAScript對象來表示一個變量對象: ~~~ VO = {}; ~~~ 就像我們所說的, VO就是執行上下文的屬性(property): ~~~ activeExecutionContext = { VO: { // 上下文數據(var, FD, function arguments) } }; ~~~ 只有全局上下文的變量對象允許通過VO的屬性名稱來間接訪問(因為在全局上下文里,全局對象自身就是變量對象,稍后會詳細介紹),在其它上下文中是不能直接訪問VO對象的,因為它只是內部機制的一個實現。 當我們聲明一個變量或一個函數的時候,和我們創建VO新屬性的時候一樣沒有別的區別(即:有名稱以及對應的值)。 例如: ~~~ var a = 10; function test(x) { var b = 20; }; test(30); ~~~ 對應的變量對象是: ~~~ // 全局上下文的變量對象 VO(globalContext) = { a: 10, test: function> }; // test函數上下文的變量對象 VO(test functionContext) = { x: 30, b: 20 }; ~~~ 在具體實現層面(以及規范中)變量對象只是一個抽象概念。(從本質上說,在具體執行上下文中,VO名稱是不一樣的,并且初始結構也不一樣。 ## 不同執行上下文中的變量對象 對于所有類型的執行上下文來說,變量對象的一些操作(如變量初始化)和行為都是共通的。從這個角度來看,把變量對象作為抽象的基本事物來理解更為容易。同樣在函數上下文中也定義和變量對象相關的額外內容。 ~~~ 抽象變量對象VO (變量初始化過程的一般行為) ║ ╠══> 全局上下文變量對象GlobalContextVO ║ (VO === this === global) ║ ╚══> 函數上下文變量對象FunctionContextVO (VO === AO, 并且添加了<arguments>和<formal parameters>) ~~~ 我們來詳細看一下: ### 全局上下文中的變量對象 首先,我們要給全局對象一個明確的定義 > 全局對象(Global object) 是在進入任何執行上下文之前就已經創建了的對象; > 這個對象只存在一份,它的屬性在程序中任何地方都可以訪問,全局對象的生命周期終止于程序退出那一刻。 全局對象初始創建階段將Math、String、Date、parseInt作為自身屬性,等屬性初始化,同樣也可以有額外創建的其它對象作為屬性(其可以指向到全局對象自身)。例如,在DOM中,全局對象的window屬性就可以引用全局對象自身(當然,并不是所有的具體實現都是這樣): ~~~ global = { Math: <...>, String: <...> ... ... window: global //引用自身 }; ~~~ 當訪問全局對象的屬性時通常會忽略掉前綴,這是因為全局對象是不能通過名稱直接訪問的。不過我們依然可以通過全局上下文的this來訪問全局對象,同樣也可以遞歸引用自身。例如,DOM中的window。綜上所述,代碼可以簡寫為: ~~~ String(10); // 就是global.String(10); // 帶有前綴 window.a = 10; // === global.window.a = 10 === global.a = 10; this.b = 20; // global.b = 20; ~~~ 因此,回到全局上下文中的變量對象——在這里,變量對象就是全局對象自己: ~~~ VO(globalContext) === global; ~~~ 非常有必要要理解上述結論,基于這個原理,在全局上下文中聲明的對應,我們才可以間接通過全局對象的屬性來訪問它(例如,事先不知道變量名稱)。 ~~~ var a = new String('test'); alert(a); // 直接訪問,在VO(globalContext)里找到:"test" alert(window['a']); // 間接通過global訪問:global === VO(globalContext): "test" alert(a === this.a); // true var aKey = 'a'; alert(window[aKey]); // 間接通過動態屬性名稱訪問:"test" ~~~ ### 函數上下文中的變量對象 在函數執行上下文中,VO是不能直接訪問的,此時由活動對象(activation object,縮寫為AO)扮演VO的角色。 ~~~ VO(functionContext) === AO; ~~~ 活動對象是在進入函數上下文時刻被創建的,它通過函數的arguments屬性初始化。arguments屬性的值是Arguments對象: ~~~ AO = { arguments: <ArgO> }; ~~~ Arguments對象是活動對象的一個屬性,它包括如下屬性: 1. callee — 指向當前函數的引用 2. length — 真正傳遞的參數個數 3. properties-indexes (字符串類型的整數) 屬性的值就是函數的參數值(按參數列表從左到右排列)。 properties-indexes內部元素的個數等于arguments.length. properties-indexes 的值和實際傳遞進來的參數之間是共享的。 例如: ~~~ function foo(x, y, z) { // 聲明的函數參數數量arguments (x, y, z) alert(foo.length); // 3 // 真正傳進來的參數個數(only x, y) alert(arguments.length); // 2 // 參數的callee是函數自身 alert(arguments.callee === foo); // true // 參數共享 alert(x === arguments[0]); // true alert(x); // 10 arguments[0] = 20; alert(x); // 20 x = 30; alert(arguments[0]); // 30 // 不過,沒有傳進來的參數z,和參數的第3個索引值是不共享的 z = 40; alert(arguments[2]); // undefined arguments[2] = 50; alert(z); // 40 } foo(10, 20); ~~~ 這個例子的代碼,在當前版本的Google Chrome瀏覽器里有一個bug? — 即使沒有傳遞參數z,z和arguments[2]仍然是共享的。 ## 處理上下文代碼的2個階段 現在我們終于到了本文的核心點了。執行上下文的代碼被分成兩個基本的階段來處理: 1. 進入執行上下文 2. 執行代碼 變量對象的修改變化與這兩個階段緊密相關。 > 注:這2個階段的處理是一般行為,和上下文的類型無關(也就是說,在全局上下文和函數上下文中的表現是一樣的)。 ### 進入執行上下文 當進入執行上下文(代碼執行之前)時,VO里已經包含了下列屬性(前面已經說了): ??? **函數的所有形參(如果我們是在函數執行上下文中)** ??? — 由名稱和對應值組成的一個變量對象的屬性被創建;沒有傳遞對應參數的話,那么由名稱和undefined值組成的一種變量對象的屬性也將被創建。 ??? **所有函數聲明(FunctionDeclaration, FD)** ??? —由名稱和對應值(函數對象(function-object))組成一個變量對象的屬性被創建;如果變量對象已經存在相同名稱的屬性,則完全替換這個屬性。 ??? **所有變量聲明(var, VariableDeclaration)** ??? — 由名稱和對應值(undefined)組成一個變量對象的屬性被創建;如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。 讓我們看一個例子: ~~~ function test(a, b) { var c = 10; function d() {} var e = function _e() {}; (function x() {}); } test(10); // call ~~~ 當進入帶有參數10的test函數上下文時,AO表現為如下: ~~~ AO(test) = { a: 10, b: undefined, c: undefined, d: <reference to FunctionDeclaration "d"> e: undefined }; ~~~ 注意,AO里并不包含函數“x”。這是因為“x” 是一個函數表達式(FunctionExpression, 縮寫為 FE) 而不是函數聲明,函數表達式不會影響VO。 不管怎樣,函數“_e” 同樣也是函數表達式,但是就像我們下面將看到的那樣,因為它分配給了變量 “e”,所以它可以通過名稱“e”來訪問。 函數聲明FunctionDeclaration與函數表達式FunctionExpression 的不同,將在第15章Functions進行詳細的探討,也可以參考本系列第2章[揭秘命名函數表達](http://www.cnblogs.com/TomXu/archive/2011/12/29/2290308.html)式來了解。 這之后,將進入處理上下文代碼的第二個階段 — 執行代碼。 ### 代碼執行 這個周期內,AO/VO已經擁有了屬性(不過,并不是所有的屬性都有值,大部分屬性的值還是系統默認的初始值undefined )。 還是前面那個例子, AO/VO在代碼解釋期間被修改如下: ~~~ AO['c'] = 10; AO['e'] = <reference to FunctionExpression "_e">; ~~~ 再次注意,因為FunctionExpression“_e”保存到了已聲明的變量“e”上,所以它仍然存在于內存中。而FunctionExpression “x”卻不存在于AO/VO中,也就是說如果我們想嘗試調用“x”函數,不管在函數定義之前還是之后,都會出現一個錯誤“x is not defined”,未保存的函數表達式只有在它自己的定義或遞歸中才能被調用。 另一個經典例子: ~~~ alert(x); // function var x = 10; alert(x); // 10 x = 20; function x() {}; alert(x); // 20 ~~~ 為什么第一個alert “x” 的返回值是function,而且它還是在“x” 聲明之前訪問的“x” 的?為什么不是10或20呢?因為,根據規范函數聲明是在當**進入上下文**時填入的; 同意周期,在進入上下文的時候還有一個變量聲明“x”,那么正如我們在上一個階段所說,變量聲明在順序上跟在函數聲明和形式參數聲明之后,而且在這個**進入上下文**階段,變量聲明不會干擾VO中已經存在的同名函數聲明或形式參數聲明,因此,在進入上下文時,VO的結構如下: ~~~ VO = {}; VO['x'] = <reference to FunctionDeclaration "x"> // 找到var x = 10; // 如果function "x"沒有已經聲明的話 // 這時候"x"的值應該是undefined // 但是這個case里變量聲明沒有影響同名的function的值 VO['x'] = <the value is not disturbed, still function> ~~~ 緊接著,在執行代碼階段,VO做如下修改: ~~~ VO['x'] = 10; VO['x'] = 20; ~~~ 我們可以在第二、三個alert看到這個效果。 在下面的例子里我們可以再次看到,變量是在進入上下文階段放入VO中的。(因為,雖然else部分代碼永遠不會執行,但是不管怎樣,變量“b”仍然存在于VO中。) ~~~ if (true) { var a = 1; } else { var b = 2; } alert(a); // 1 alert(b); // undefined,不是b沒有聲明,而是b的值是undefined ~~~ ## 關于變量 通常,各類文章和JavaScript相關的書籍都聲稱:“不管是使用var關鍵字(在全局上下文)還是不使用var關鍵字(在任何地方),都可以聲明一個變量”。請記住,這是錯誤的概念: 任何時候,變量只能通過使用var關鍵字才能聲明。 上面的賦值語句: ~~~ a = 10; ~~~ 這僅僅是給全局對象創建了一個新屬性(但它不是變量)。“不是變量”并不是說它不能被改變,而是指它不符合ECMAScript規范中的變量概念,所以它“不是變量”(它之所以能成為全局對象的屬性,完全是因為VO(globalContext) === global,大家還記得這個吧?)。 讓我們通過下面的實例看看具體的區別吧: ~~~ alert(a); // undefined alert(b); // "b" 沒有聲明 b = 10; var a = 20; ~~~ 所有根源仍然是VO和進入上下文階段和代碼執行階段: 進入上下文階段: ~~~ VO = { a: undefined }; ~~~ 我們可以看到,因為“b”不是一個變量,所以在這個階段根本就沒有“b”,“b”將只在代碼執行階段才會出現(但是在我們這個例子里,還沒有到那就已經出錯了)。 讓我們改變一下例子代碼: ~~~ alert(a); // undefined, 這個大家都知道, b = 10; alert(b); // 10, 代碼執行階段創建 var a = 20; alert(a); // 20, 代碼執行階段修改 ~~~ 關于變量,還有一個重要的知識點。變量相對于簡單屬性來說,變量有一個特性(attribute):{DontDelete},這個特性的含義就是不能用delete操作符直接刪除變量屬性。 ~~~ a = 10; alert(window.a); // 10 alert(delete a); // true alert(window.a); // undefined var b = 20; alert(window.b); // 20 alert(delete b); // false alert(window.b); // still 20 ~~~ 但是這個規則在有個上下文里不起走樣,那就是eval上下文,變量沒有{DontDelete}特性。 ~~~ eval('var a = 10;'); alert(window.a); // 10 alert(delete a); // true alert(window.a); // undefined ~~~ 使用一些調試工具(例如:Firebug)的控制臺測試該實例時,請注意,Firebug同樣是使用eval來執行控制臺里你的代碼。因此,變量屬性同樣沒有{DontDelete}特性,可以被刪除。 ## 特殊實現: __parent__ 屬性 前面已經提到過,按標準規范,活動對象是不可能被直接訪問到的。但是,一些具體實現并沒有完全遵守這個規定,例如SpiderMonkey和Rhino;的實現中,函數有一個特殊的屬性 __parent__,通過這個屬性可以直接引用到活動對象(或全局變量對象),在此對象里創建了函數。 例如 (SpiderMonkey, Rhino): ~~~ var global = this; var a = 10; function foo() {} alert(foo.__parent__); // global var VO = foo.__parent__; alert(VO.a); // 10 alert(VO === global); // true ~~~ 在上面的例子中我們可以看到,函數foo是在全局上下文中創建的,所以屬性__parent__ 指向全局上下文的變量對象,即全局對象。 然而,在SpiderMonkey中用同樣的方式訪問活動對象是不可能的:在不同版本的SpiderMonkey中,內部函數的__parent__ 有時指向null ,有時指向全局對象。 在Rhino中,用同樣的方式訪問活動對象是完全可以的。 例如 (Rhino): ~~~ var global = this; var x = 10; (function foo() { var y = 20; // "foo"上下文里的活動對象 var AO = (function () {}).__parent__; print(AO.y); // 20 // 當前活動對象的__parent__ 是已經存在的全局對象 // 變量對象的特殊鏈形成了 // 所以我們叫做作用域鏈 print(AO.__parent__ === global); // true print(AO.__parent__.x); // 10 })(); ~~~ ## 總結 在這篇文章里,我們深入學習了跟執行上下文相關的對象。我希望這些知識對您來說能有所幫助,能解決一些您曾經遇到的問題或困惑。按照計劃,在后續的章節中,我們將探討作用域鏈,標識符解析,閉包。 有任何問題,我很高興在下面評論中能幫你解答。 ## 其它參考 * 10.1.3 – [Variable Instantiation](http://bclary.com/2004/11/07/#a-10.1.3 "Variable Instantiation"); * 10.1.5 – [Global Object](http://bclary.com/2004/11/07/#a-10.1.5 "Global Object"); * 10.1.6 – [Activation Object](http://bclary.com/2004/11/07/#a-10.1.6 "Activation Object"); * 10.1.8 – [Arguments Object](http://bclary.com/2004/11/07/#a-10.1.8 "Arguments Object").
                  <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>

                              哎呀哎呀视频在线观看