理解Javascript之執行上下文(Execution Context)

1>什麼是執行上下文javascript

Javascript中代碼的運行環境分爲如下三種:
全局級別的代碼 - 這個是默認的代碼運行環境,一旦代碼被載入,引擎最早進入的就是這個環境。
函數級別的代碼 - 當執行一個函數時,運行函數體中的代碼。
Eval的代碼 - 在Eval函數內運行的代碼。
javascript是一個單線程語言,這意味着在瀏覽器中同時只能作一件事情。當javascript解釋器初始執行代碼,它首先默認進入全局上下文。每次調用一個函數將會建立一個新的執行上下文。
每次新建立的一個執行上下文會被添加到做用域鏈的頂部,有時也稱爲執行或調用棧。瀏覽器老是運行位於做用域鏈頂部的當前執行上下文。一旦完成,當前執行上下文將從棧頂被移除而且將控制權歸還給以前的執行上下文。java

不一樣執行上下文之間的變量命名衝突經過攀爬做用域鏈解決,從局部直到全局。這意味着具備相同名稱的局部變量在做用域鏈中有更高的優先級。
簡單的說,每次你試圖訪問函數執行上下文中的變量時,查找進程老是從本身的變量對象開始。若是在本身的變量對象中沒發現要查找的變量,繼續搜索做用域鏈。它將攀爬做用域鏈檢查每個執行上下文的變量對象,尋找和變量名稱匹配的值。瀏覽器

2>執行上下文的創建過程函數

咱們如今已經知道,每當調用一個函數時,一個新的執行上下文就會被建立出來。然而,在javascript引擎內部,這個上下文的建立過程具體分爲兩個階段:
創建階段(發生在當調用一個函數時,可是在執行函數體內的具體代碼之前)
創建變量,函數,arguments對象,參數
創建做用域鏈
肯定this的值
代碼執行階段:
變量賦值,函數引用,執行其它代碼
實際上,能夠把執行上下文看作一個對象,其下包含了以上3個屬性:this

executionContextObj = { variableObject: { /* 函數中的arguments對象, 參數,
內部的變量以及函數聲明 / }, scopeChain: { / variableObject
以及全部父執行上下文中的variableObject */ }, this: {} }線程

3>創建階段以及代碼執行階段的詳細分析
確 切地說,執行上下文對象(上述的executionContextObj)是在函數被調用時,可是在函數體被真正執行之前所建立的。函數被調用時,就是我 上述所描述的兩個階段中的第一個階段 - 創建階段。這個時刻,引擎會檢查函數中的參數,聲明的變量以及內部函數,而後基於這些信息創建執行上下文對象 (executionContextObj)。在這個階段,variableObject對象,做用域鏈,以及this所指向的對象都會被肯定。
上述第一個階段的具體過程以下:code

1.找到當前上下文中的調用函數的代碼
2.在執行被調用的函數體中的代碼之前,開始建立執行上下文
3.進入第一個階段-創建階段:
    創建variableObject對象:
        創建arguments對象,檢查當前上下文中的參數,創建該對象下的屬性以及屬性值
        檢查當前上下文中的函數聲明:
        每找到一個函數聲明,就在variableObject下面用函數名創建一個屬性,屬性值就是指向該函數在內存中的地址的一個引用
        若是上述函數名已經存在於variableObject下,那麼對應的屬性值會被新的引用所覆蓋。
        檢查當前上下文中的變量聲明:
        每找到一個變量的聲明,就在variableObject下,用變量名創建一個屬性,屬性值爲undefined。
        若是該變量名已經存在於variableObject屬性中,直接跳過(防止指向函數的屬性的值被變量屬性覆蓋爲undefined),原屬性值不會被修改。
    初始化做用域鏈
    肯定上下文中this的指向對象
4.代碼執行階段:
    執行函數體中的代碼,一行一行地運行代碼,給variableObject中的變量屬性賦值。
    下面來看個具體的代碼示例:
    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: { ... }
}
咱們看到,只有在代碼執行階段,變量屬性纔會被賦予具體的值。

4>局部變量做用域提高的原因
在網上一直看到這樣的總結: 在函數中聲明的變量以及函數,其做用域提高到函數頂部,換句話說,就是一進入函數體,就能夠訪問到其中聲明的變量以及函數。這是對的,可是知道其中的原因嗎?相信你經過上述的解釋應該也有所明白了。不過在這邊再分析一下。看下面一段代碼:對象

function() {

   console.log(typeof foo); // function pointer
   console.log(typeof bar); // undefined

   var foo = 'hello',
       bar = function() {
           return 'world';
       };

   function foo() {
       return 'hello';
   }

}());
上述代碼定義了一個匿名函數,而且經過()運算符強制理解執行。那麼咱們知道這個時候就會有個執行上下文被建立,咱們看到例子中立刻能夠訪問foo以及bar變量,而且經過typeof輸出foo爲一個函數引用,bar爲undefined。
爲何咱們能夠在聲明foo變量之前就能夠訪問到foo呢?

因 爲在上下文的創建階段,先是處理arguments, 參數,接着是函數的聲明,最後是變量的聲明。那麼,發現foo函數的聲明後,就會在variableObject下面創建一個foo屬性,其值是一個指向 函數的引用。當處理變量聲明的時候,發現有var foo的聲明,可是variableObject已經具備了foo屬性,因此直接跳過。當進入代碼執行階段的時候,就能夠經過訪問到foo屬性了,由於它 已經就存在,而且是一個函數引用。進程

爲何bar是undefined呢?

由於bar是變量的聲明,在創建階段的時候,被賦予的默認的值爲undefined。因爲它只要在代碼執行階段纔會被賦予具體的值,因此,當調用typeof(bar)的時候輸出的值爲undefined。ip

相關文章
相關標籤/搜索