深究JavaScript——執行上下文

定義

執行上下文(也稱執行環境execution context)(簡稱:EC)是個抽象的概念,是在函數被調用時,可是在函數體被真正執行之前所建立的。每一個執行上下文都有一個變量對象(variable object),保存着當前環境中全部的變量或函數聲明或形參arguments以及this。前端

   一系列活動的執行上下文從邏輯上造成一個棧。棧底老是全局上下文,棧頂是當前(活動的)執行上下文。當執行流進入函數時,函數的環境就會被推入一個環境棧中,在函數執行以後,棧再將其環境彈出,把控制權返回給以前的執行環境。
   代碼在一個環境中執行,會建立變量對象的一個做用域鏈,做用域鏈保證了執行的順序。git

三種運行環境

  • 全局:

   只存在一個全局的上下文,該上下文能被任何其它的上下文所訪問到。github

  • 函數:

   每調用執行一個函數時,引擎就會自動新建出一個函數執行上下文,就是新建一個局部做用域,能夠在該局部做用域中聲明私有變量等,在外部的上下文中是沒法直接訪問到該局部做用域內的元素的。
   當在全局上下文中調用執行一個函數時,程序流就進入該被調用函數內,此時引擎就會爲該函數建立一個新的執行上下文(即便是調用自身函數),而且將其壓入到執行上下文堆棧的頂部。瀏覽器老是執行當前在堆棧頂部的上下文,一旦執行完畢,該上下文就會從堆棧頂部被彈出,而後,進入最新的堆棧頂部的上下文執行代碼。堆棧中的上下文就會被依次執行而且彈出堆棧,直到回到全局的上下文。瀏覽器

  • eval函數

  在eval函數內運行的代碼。函數

執行上下文創建、運行過程

創建階段:

發生在調用一個函數時,可是在執行函數體內具體的代碼之前。this

創建variableObject對象

先是處理arguments參數,接着是函數的聲明,最後是變量的聲明。code

  創建arguments對象,檢查當前上下文中的參數,創建該對象下的屬性以及屬性值。每一個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的全部變量和函數都保存在這個對象中。雖然咱們編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它。對象

  檢查當前上下文中的函數聲明:找到函數聲明的函數名,在variableObject下面用該函數名創建一個屬性,屬性值就是指向該函數在內存中的地址的一個引用;若是上述函數名已經存在於variableObject下,那麼對應的屬性值會被新的引用所覆蓋。blog

   檢查當前上下文中的變量聲明:每找到一個變量的聲明,就在variableObject下,用變量名創建一個屬性,屬性值爲undefined。若是該變量名已經存在於variableObject屬性中,直接跳過(防止指向函數的屬性的值被變量屬性覆蓋爲undefined),原屬性值不會被修改。ip

初始化做用域鏈

  每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。 ECMAScript 程序中的執行流正是由這個方便的機制控制着。當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈(scope chain)。做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象(activation object)做爲變量對象。活動對象在最開始時只包含一個變量,即 arguments 對象(這個對象在全局環境中是不存在的)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。

肯定上下文中this的指向對象

  肯定this指向window或當前對象。

代碼執行階段

  1. 給variableObject中的變量賦值
function aoo(i){
    var a = 'staven';
    var b = function boo(){
        
    };
    function coo(){
        
    }
}
aoo(1);
  1. 調用aoo(1),創建階段執行下上文對象:
aooExecutionContext = {
    variableObject:{
        arguments:{
            0:1,
            length:1
        },
        i:1,
        coo:(指向function coo()),
        a:undefined,
        b:undefined
    },
    scopeChain:{……},
    this:{……}
};
  1. 運行階段,執行上下文對象:
aooExecutionContext = {
    variableObject:{
        arguments:{
            0:1,
            length:1
        },
        i:1,
        coo:(指向function coo()),
        a:"staven",
        b:(指向function boo())
    },
    scopeChain:{……},
    this:{……}
};

解析變量提高

(function(){
    console.log(typeof foo);  //function
    console.log(typeof bar); //undefined
    
    var foo = 'hello',
        bar = function(){
            return "staven";
        };
    function foo(){
        return "hello";
    }
    console.log(typeof foo); //string
    console.log(typeof bar); //function
}());

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


☞☞☞深究JavaScript系列☜☜☜

相關文章
相關標籤/搜索