JavaScript中的做用域鏈原理

執行環境express

 做用域鏈的造成與執行環境(Execution Environment)相關,在JavaScript當中,產生執行環境有以下3中情形:閉包

1 進入全局環境函數

2 調用eval函數this

3 調用functionspa

  在一個執行環境A上能夠建立執行環境B,執行環境B又能夠建立執行環境C...,這一系列的執行環境構成執行環境棧,最新建立的執行環境位於棧頂(棧底永遠是全局執行環境),當棧頂執行環境結束以後(與之相關的代碼執行結束)就會被彈出站外,底下的執行環境就會成爲新的棧頂。以下圖所示:指針

 

  一個執行環境由3部分組成:LexicalEnvironment,VariableEnvironment,ThisBinding。其中的ThisBinding就是this值,而LexicalEnvironment和VariableEnvironent至關於兩個指針,它們指向Lexical Environment(注意不是LexicalEnvironment),Lexical Environment裏面包含的就是標識符。code

  Lexical Environment又由2部分組成:Environment Record和一個outer指針,其中outer指針指向當前執行環境下面執行環境的Lexical Environment,而Environment Record裏面就是綁定的程序中各類標示符。對象

  Environment Record又能夠細分爲2種類型:一種叫Declaration Environment Record,另外一種是Object Environment Record。這兩種類型的Environment Record都記錄程序中使用到的標識符,不一樣之處在於,Declaration Environment Record主要綁定的是變量的聲明和函數的聲明,而Object Environment Record會關聯一個Object,Object Environment Record裏面綁定的都是和這個變量相關的屬性(好比with語法),這樣在JavaScript代碼裏面訪問這個變量的屬性時就能夠不用顯示指定該Object。blog

  整個關係以下圖所示:ip

  須要注意的是,執行環境棧最底層的全局執行環境的outer指針指向null。當查找某一個標示符時,就從最頂端執行環境的Lexical Environment開始查找,若是找不到,就進入到outer指針指向的下一個Lexical Environment裏面進行查找(上面的圖示是爲了演示,實際中outer指針指向的Lexical Environment有可能不是相鄰執行環境的Lexical Environment),直到查找到該變量或者outer指針指向的是null,所以,由outer指針鏈接的Lexical Environment就是當前函數運行時的做用域鏈。

  在執行環境剛創建的過程當中,LexicalEnvironment和VariableEnvironment指向的同一個Lexical Environment,可是若是在該執行環境下遇到了特殊的語法,好比with,那麼LexicalEnvironment就會指向新的Lexical Record,當with運行結束,LexicalEnvironment又會指向VariableEnvironment所指的Lexical Environment,以下面代碼所示:

function f() {
    var i = 1;

    with(document) {
        write("Hello");
    }

    var j = 2;
}

在執行with語句以前的執行環境以下圖所示:

執行with語句時執行環境以下圖所示(注意沒有建立新的執行環境,只是增長了一個Lexical Environment):

執行with語句後執行環境以下圖所示:

 

Environment Record的構成

  無論Environment Record是Declaration Environment Record,仍是Object Environment Record,都綁定的是當前執行環境下的標示符的信息,即Key-Value,Key就是標示符名,Value就是對應的值,以下圖所示:

 

進入函數造成執行環境

假設有以下代碼:

function f(a, b, c) {
    var i = 1;
    var j = 2;
    

    //函數聲明(function declaration)
    function inner(k, l, m) {
        var i = 1;
        var p = 2;
        var q = 3;
    }

    //函數表達式(function expression)
    var fVar = function(x, y, z) {
        var i = 1;
        var r = 2;
        var s = 3;
    }
}

當在JavaScript當中以f(1, 2, 3)調用這個函數時,就會爲這個函數建立新執行環境,在執行該函數內部任何代碼以前,會先將函數裏面的標示符信息放到Environment Record當中。Environment Record當中的綁定標示符能夠分紅4部分(這4個部分的順序在Environment Record當中固定,而且每一部份內部的順序依據標識符出現的前後):函數參數,函數聲明,arguments,變量聲明,通過綁定後,函數f的執行環境以下:

 

   參數標示符直接綁定的是傳給函數的實參值;

  函數聲明標示符綁定的是函數對象(這也是函數聲明標示符能夠先使用,後聲明緣由,由於在代碼運行以前,函數聲明標示符就已經加入到了Environment Record中,而且綁定了函數對象);

  arguments綁定的是arguments對象;

  而變量聲明(包括函數表達式,也能夠當作是變量聲明),綁定的都是undefined,只有當代碼真正運行到賦值給變量的語句,變量纔會持有會變成咱們賦予變量的值(這也是在變量聲明以前可使用該變量,可是變量值老是undefined的緣由)。

  上圖中Environment Record裏面的變量i和j是函數f裏面的變量,與函數聲明inner以及函數表達式fVar裏面的變量無關,由於此時只是對它們進行了定義,尚未進行調用。

須要注意的是:

1) Environment Record會綁定arguments標示符的條件是參數名和函數聲明的標識符沒有命名爲arguments的;

2) 若是多個函數使用同一個標識符,Environment Record也只會有一條記錄,而且該記錄綁定的是最後聲明的函數對象,即後聲明的函數對象覆蓋以前的函數對象,而且若是函數聲明標識符合參數標識符重名,那麼函數標識符覆蓋參數標識符,即若是有以下代碼:

function f(a, b, c) {
    alert(a);
    function a() {
       ...
    }
    alert(a);
}

當使用f(1, 2, 3)調用時,兩處alert都會顯示function a,而不是參數a。

3)若是變量聲明標識符與以前參數,函數聲明,arguments標識符重名,那麼變量標識符不會被綁定,即若是有下面代碼:

function f(a, b, c) {
    function i() {
      ...
    }

    alert(i);
    var i = 1;
    alert(i);
}

當使用f(1, 2, 3)調用時,第一處的alert會顯示function i,而不是undefined值,由於變量聲明i與函數i重名,不會被綁定;第二個alert會顯示1,由於運行了var i =1;以後,Environment Record裏面標識符i對應的值被賦予了1。

同時,若是屢次聲明同一個變量,Environment Record裏面也只會有一條記錄。

  剩下的一個問題是,在調用函數的時候,新的執行環境是如何與外層的執行環境聯繫到一塊兒的,即新執行環境的outer指針是如何知道該指向哪個執行環境的。答案就是,在定義函數時,函數對象的內部屬性[[scope]]會引用定義本身時所在的Lexical Environment,當發生調用時,outer指針就指向[[scope]]所引用的Lexical Environment。假設有以下代碼:

function f1() {
    var f = f2(); 
    f();  
}

function f2() {
    var i = 1;
    return function() {
            i = 2;
    }
}

當調用f2時,執行環境以下圖所示:

當調用f2結束時,執行環境以下圖所示:

f2調用結束,f2相關的執行環境從棧頂彈出,而f2執行環境引用的Lexical Environment因爲有函數對象f的[[scope]]屬性引用,所以不會隨着f2的執行環境彈出棧頂而被垃圾回收。

當調用函數f時,執行環境以下圖所示:

從圖上能夠看到,從最頂端f的Lexical Environment,沿着outer指針,函數f能夠訪問到函數f2裏面的局部變量i,這就是閉包的原理。

 

參考資料

ECMA-262

相關文章
相關標籤/搜索