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

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] >http://blog.csdn.net/z742182637/article/category/6047401/2 # 深入理解Javascript之執行上下文(Execution Context) 在這篇文章中,將比較深入地闡述下執行上下文 - Javascript 中最基礎也是最重要的一個概念。相信讀完這篇文章后,你就會明白 javascript 引擎內部在執行代碼以前到底做了些什么,為什么某些函數以及變量在沒有被聲明以前就可以被使用,以及它們的最終的值是怎樣被定義的。 ## 什么是執行上下文(**EC**) Javascript中代碼的運行環境分為以下三種: * 全局級別的代碼 - 這個是默認的代碼運行環境,一旦代碼被載入,引擎最先進入的就是這個環境。 * 函數級別的代碼 - 當執行一個函數時,運行函數體中的代碼。 * `eval`的代碼 - 在`eval`函數內運行的代碼。 在網上可以找到很多闡述作用域的資源,為了使該文便于大家理解,我們可以將“執行上下文”看做當前代碼的運行環境或者作用域。 下面我們來看一個示例,其中包括了全局以及函數級別的執行上下文: ![](https://box.kancloud.cn/b6addd1c134b1034fdacb797db2e651c_554x447.png) 上圖中,一共用4個執行上下文。 * <i style="color:#BE00B3">紫色的代表全局的上下文;</i> * <i style="color:#22CC01">綠色代表person函數內的上下文;</i> * <i style="color:#0036FF">藍色</i>以及<i style="color:#FF9600">橙色</i>代表 person 函數內的另外兩個函數的上下文。 注意,不管什么情況下,只存在一個全局的上下文,該上下文能被任何其它的上下文所訪問到。也就是說,我們可以在 person 的上下文中訪問到全局上下文中的 sayHello 變量,當然在函數 firstName 或者 lastName 中同樣可以訪問到該變量。 ## 執行上下文堆棧(ECS) 一系列活動的執行上下文從邏輯上形成一個棧。**棧底總是全局上下文,棧頂是當前(活動的)執行上下文**。當在不同的執行上下文間切換(退出的而進入新的執行上下文)的時候,棧會被修改(通過壓棧或者退棧的形式)。 **壓棧:**全局EC—>局部EC1—>局部EC2—>當前EC **出棧:**全局EC<—局部EC1<—局部EC2<—當前EC 我們可以用數組的形式來表示環境棧: ``` ECS=[局部EC,全局EC]; ``` 每次控制器進入一個函數(哪怕該函數被遞歸調用或者作為構造器),都會發生壓棧的操作。過程類似 javascript 數組的 push 和 pop 操作。 在瀏覽器中,javascript 引擎的工作方式是單線程的。也就是說,某一時刻**只會有一個事件是被激活處理的**,其它的事件被放入隊列中,等待被處理。 下面的示例圖描述了這樣的一個堆棧: ![](https://box.kancloud.cn/df4e634fd5133eb8ae456e6ad5bd880c_555x288.png) 我們已經知道,**當javascript代碼文件被瀏覽器載入后,默認最先進入的是一個全局的執行上下文**。 當在全局上下文中調用執行一個函數時,程序流就進入該被調用函數內,此時引擎就會為該函數創建一個新的執行上下文,并且將其壓入到執行上下文堆棧的頂部。 **瀏覽器總是執行當前在堆棧頂部的上下文,一旦執行完畢,該上下文就會從堆棧頂部被彈出,然后,進入其下的上下文執行代碼。last-in first-out stack (LIFO stack)** 這樣,堆棧中的上下文就會被依次執行并且彈出堆棧,直到回到全局的上下文。請看下面一個例子: ```js (function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0)); ``` 上述 `foo` 被聲明后,通過 `()` 運算符立即執行運行了。函數代碼就是調用了其自身3次,每次是局部變量 `i` 增加1。每次 `foo` 函數被自身調用時,就會有一個新的執行上下文被創建。每當一個上下文執行完畢,該上上下文就被彈出堆棧,回到上一個上下文,直到再次回到全局上下文。整個過程抽象如下圖: ![](https://box.kancloud.cn/b41621c52e822cb1754d99ab14e6fcfa_390x259.png) 由此可見 ,對于執行上下文這個抽象的概念,可以歸納為以下幾點: * 單線程 * 同步執行 * 唯一的一個全局上下文 * 函數的執行上下文的個數沒有限制 * 每次函數被調用創建新的執行上下文,包括調用自己。 ## 執行上下文的建立過程 我們現在已經知道,**每當調用一個函數時,一個新的執行上下文就會被創建出來**。 JavaScript 代碼自上而下執行,但是在 js 代碼執行前,javascript 引擎內部會首先進行詞法分析,所以事實上,js 運行要分為**預編譯的詞法分析**和**實際執行**兩個階段: ### 預編譯階段(進入上下文階段,會進行一系列的詞法分析,發生在當調用一個函數時,但是在執行函數體內的具體代碼以前) ![](https://box.kancloud.cn/5c6be302e9d98ca677c4d93df4245f65_387x315.png) * 建立變量,函數,`arguments` 對象,參數; * 建立作用域鏈; * 確定 this 的值; #### 創建變量對象 **創建變量對象**主要是經過以下過程,如圖所示: ![](https://box.kancloud.cn/33c89d0faf54f4076bca04eae4e284ca_590x232.png) 1. 創建 `arguments` 對象,檢查當前上下文的參數,建立該對象的屬性與屬性值,僅在函數環境(非箭頭函數)中進行的,全局環境沒有此過程。 2. 檢查當前上下文的**函數聲明**,按照代碼順序查找,將找到的函數提前聲明,如果當前上下文的變量對象沒有該函數名屬性,則在該變量對象以函數名建立一個屬性,屬性值則指向該函數所在**堆內存地址引用**,如果存在,則會被新的引用覆蓋掉。 3. 檢查當前上下文的**變量聲明**,愛去哪找代碼順序查找,將找到的變量提前聲明,如果當前上下文的變量對象沒有變量名屬性,則在該變量對象以變量名建立一個屬性,屬性值為 `undefined`;如果存在,則忽略該變量聲明。 **函數聲明提前和變量聲明提升是在創建變量對象中進行的,且函數聲明優先級高于變量聲明**。 **創建變量對象發生在預編譯階段,還沒有進入到執行階段,該變量對象都不能訪問的**,因為此時的變量對象中的變量屬性尚未賦值,值仍為 `undefined`,只有在進行執行階段,變量中的變量屬性才進行賦值后,變量對象(Variable Object)轉為活動對象(Active Object)后,才能進行訪問,這個過程就是 VO -> AO 過程。 ### 執行階段 變量賦值,函數引用,執行其它代碼; 實際上,可以把執行上下文看做一個對象,其下包含了以上3個屬性: ~~~ executionContextObj = { variableObject: { /* 函數中的arguments對象, 參數, 內部的變量以及函數聲明 */ }, scopeChain: { /* variableObject 以及所有父執行上下文中的variableObject */ }, this: {} } ~~~ > 進入執行上下文時,**VO**(variableObject)的初始化過程具體如下: > 函數的形參(當進入函數執行上下文時)—— 變量對象的一個屬性,其屬性名就是形參的名字,其值就是實參的值;對于沒有傳遞的參數,其值為 `undefined`; > 函數聲明(FunctionDeclaration, **FD**) —— 變量對象的一個屬性,其屬性名和值都是函數對象創建出來的;**如果變量對象已經包含了相同名字的屬性,則替換它的值**; > 變量聲明(var,VariableDeclaration) —— 變量對象的一個屬性,其屬性名即為變量名,其值為 `undefined`;如果變量名和已經聲明的函數名或者函數的參數名相同,則不會影響已經存在的屬性。 **注意:該過程是有先后順序的。** > 執行代碼階段時,VO 中的一些屬性 `undefined` 值將會確定。 ## **建立階段以及代碼執行階段的詳細分析** 確切地說,執行上下文對象(上述的 executionContextObj )是在函數被調用時,但是在函數體被真正執行以前所創建的。函數被調用時,就是我上述所描述的兩個階段中的第一個階段 - 建立階段。 這個時刻,引擎會檢查函數中的參數,聲明的變量以及內部函數,然后基于這些信息建立執行上下文對象(executionContextObj)。 在這個階段,variableObject 對象,作用域鏈,以及 this 所指向的對象都會被確定。 ### AO 活動對象 在函數的執行上下文中,VO 是不能直接訪問的。它主要扮演被稱作活躍對象(activation object)(簡稱:**AO**)的角色。 這句話怎么理解呢,就是當 EC 環境為函數時,我們訪問的是 AO,而不是 VO。 不理解,可以看一下 JavaScript高級程序設計的原話: ~~~ function compare(value1,value2){ if (value1<value2){ return -1; } else if (value1>value2){ return 1; } else { return 0; } } var result = compare(5,10) ~~~ 以上代碼定義了`compare()`函數,然后又在全局作用域中調用了它(定義了變量`result`,賦值`compare(5,10)`) 當調用`compare()`時,會創建一個包含`arguments`、`value1`、`value2`的 **活動對象**。 全局執行環境的**變量對象**(包含`result`和`compare`)。 也就是說:在全局環境中,沒有了所謂的**活動對象**(AO)概念,當我們理解一個**函數的運行時**,我們就需要**變量對象**(VO)來幫助我們理解,但此時我們已經不太關心**活動對象**(AO)了,并不是它不存在了。 ``` VO(functionContext) === AO; ``` AO 是在進入函數的執行上下文時創建的,并為該對象初始化一個`arguments`屬性,該屬性的值為`arguments`對象。 ``` AO = { arguments: { callee:, length:, properties-indexes: //函數傳參參數值 } }; ``` FD 的形式只能是如下這樣: ```js function f(){ } ``` 當函數被調用是 executionContextObj 被創建,但在實際函數執行之前。這是我們上面提到的第一階段,創建階段。在此階段,解釋器掃描傳遞給函數的參數或 arguments,本地函數聲明和本地變量聲明,并創建 executionContextObj 對象。掃描的結果將完成變量對象的創建。 上述第一個階段的具體過程如下: 1. 找到當前上下文中的調用函數的代碼 2. 在執行被調用的函數體中的代碼以前,開始創建執行上下文 3. 進入第一個階段-建立階段: 建立variableObject對象: 1. 建立arguments對象,檢查當前上下文中的參數,建立該對象下的屬性以及屬性值 2. 檢查當前上下文中的函數聲明: 每找到一個函數聲明,就在variableObject下面用函數名建立一個屬性,屬性值就是指向該函數在內存中的地址的一個引用。 如果上述函數名已經存在于variableObject下,那么對應的屬性值會被新的引用所覆蓋。 3. 檢查當前上下文中的變量聲明: 每找到一個變量的聲明,就在variableObject下,用變量名建立一個屬性,屬性值為undefined。 如果該變量名已經存在于variableObject屬性中,直接跳過(防止指向函數的屬性的值被變量屬性覆蓋為undefined),原屬性值不會被修改。 初始化作用域鏈 確定上下文中 this 的指向對象 4. 代碼執行階段: 執行函數體中的代碼,一行一行地運行代碼,給`variableObject`中的變量屬性賦值。 下面來看個具體的代碼示例: ```js function foo(i) { var a = 'hello'; var b = function privateB() { }; function c() { } } foo(22); ``` 在調用`foo(22)`的時候,建立階段如下: ``` fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c:<pointer to function c()> a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } } ``` 由此可見,在建立階段,除了`arguments`,函數的聲明,以及參數被賦予了具體的屬性值,其它的變量屬性默認的都是undefined。一旦上述建立階段結束,引擎就會進入代碼執行階段,這個階段完成后,上述執行上下文對象如下: ``` fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: 'hello', b: <pointer to function privateB()> }, scopeChain: { ... }, this: { ... } } ``` 我們看到,**只有在代碼執行階段,變量屬性才會被賦予具體的值**。 ## 局部變量作用域提升的緣由 在網上一直看到這樣的總結: 在函數中聲明的變量以及函數,其作用域提升到函數頂部,換句話說,就是一進入函數體,就可以訪問到其中聲明的變量以及函數。這是對的,但是知道其中的緣由嗎?相信你通過上述的解釋應該也有所明白了。不過在這邊再分析一下。看下面一段代碼: ```js (function() { console.log(typeof foo); // function console.log(typeof bar); // undefined var foo = 'hello', bar = function() { return 'world'; }; function foo() { return 'hello'; } console.log(typeof foo); // string console.log(typeof bar); // function }()); ``` 上述代碼定義了一個匿名函數,并且通過 `()` 運算符強制理解執行。那么我們知道這個時候就會有個執行上下文被創建,我們看到例子中馬上可以訪問 `foo` 以及 `bar` 變量,并且通過 `typeof` 輸出 `foo` 為一個函數引用,`bar` 為 `undefined`。 **為什么我們可以在聲明 foo 變量以前就可以訪問到 foo 呢?** 因為在上下文的建立階段,先是處理 `arguments`, 參數,接著是函數的聲明,最后是變量的聲明。那么,發現 `foo`函數的聲明后,就會在variableObject 下面建立一個 `foo` 屬性,其值是一個指向函數的引用。當處理變量聲明的時候,發現有 `var foo` 的聲明,但是 variableObject已經具有了 `foo` 屬性,所以直接跳過。當進入代碼執行階段的時候,就可以通過訪問到 `foo` 屬性了,因為它已經就存在,并且是一個函數引用。 **為什么 `bar` 是 `undefined` 呢?** 因為`bar`是變量的聲明,在建立階段的時候,被賦予的默認的值為 `undefined`。由于它只要在代碼執行階段才會被賦予具體的值,所以,當調用 `typeof(bar)` 的時候輸出的值為 `undefined`。 到此,相信你應該對執行上下文有所理解了,這個執行上下文的概念非常重要,務必好好搞懂之! # 誰先被提升? 再來個例子,`foo` 是先提升變量聲明 還是 函數聲明 ? ```js console.log(typeof foo); // function var foo = "this is var foo"; function foo() { console.log("this is function foo"); } console.log(typeof foo) // string ``` `foo` 函數應該是先被整體提升(不是 `undefined`),然后才是 變量提升。 # 參考 《測試驅動的JavaScript開發》-第五章 函數
                  <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>

                              哎呀哎呀视频在线观看