JS 環境執行棧、變量對象、執行上下文

function test() {
        console.log(a);                // undefined
        console.log(foo());            // 2
        
        var a = 1;
        function foo() {
            return 2;
        };
    };
    test();

圖片描述

不是很準確的草圖,大概瞭解就好。數組

  • 首先JS解釋器(引擎)開始解釋代碼,構建執行環境棧(Execution Context Stack),並根據執行環境的不一樣生成不一樣的執行上下文(Execution Context)

  • 棧底永遠是全局上下文(後面說),當遇到test(),確認調用函數,就建立生成test函數本身的上下文,而後將函數執行上下文入棧(push on)到執行環境棧中。

  • testEC(test Execution Context)將會開始建立變量對象,咱們知道,當調用一個函數(激活),一個新的執行上下文就會被建立。而一個執行上下文的生命週期能夠分爲兩個階段。

    1. 建立階段:
      在這個階段中,執行上下文分別會建立變量對象、創建做用域鏈,以及肯定this的指向。瀏覽器

    2. 代碼執行階段:
      建立完成後,就會開始執行代碼,這個時候,會完成變量賦值,函數引用,以及執行其餘代碼。函數

  • 因此testEC將會生成一個變量對象,與之還有做用域鏈、this(這裏先不討論),注意,這裏是變量對象的建立階段,用VO簡示。

    變量對象的建立,經歷如下過程。測試

    1. 創建arguments對象。檢查上下文中的參數,創建該對象下的屬性與屬性值(key-value)this

    2. 檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名創建一個屬性,屬性值爲指向該函數所在內存地址的引用。若是函數名的屬性已經存在,那麼該屬性將會被新的引用所覆蓋。spa

    3. 檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名創建一個屬性,屬性值爲undefined。若是該變量名的屬性已經存在,爲了防止同名的函數被修改成undefined,則會直接跳過,原屬性值不會被修改。指針

因此,因而就有了圖中的VO引用的對象的圖示,這就是變量提高的真正緣由,對於函數聲明,它的優先級是大於變量聲明的,因此在建立階段,函數聲明的函數名就持有了函數的引用。而變量賦值須要在後面的執行階段才被賦值。code

未進入執行階段前,變量對象中的屬性都不能被訪問!可是進入執行階段以後,變量對象轉變爲了活動對象(Active Object),裏面的屬性都能被訪問了,而後開始進行執行階段的操做。對象

  • 進入執行階段,這時可稱之爲活動對象了(Active Object),將進行賦值操做。因而這時的變量就能被自由訪問了。

關於arguments對象,是個類數組結構

目前個人測試:blog

  • 函數內設有形參,可是不傳實參,遍歷arguments對象時沒有任何值

  • 函數內不設形參,可是傳實參,遍歷arguments對象有值對應的索引

function test(a,b,c,d) {
        for (var i in arguments) {
            console.log(i);
        }
        console.log(arguments.length+"個");
    };
    test(1,2,3,4);
    // 0 1 2 3
    // "4個"
    function test2(a,b,c,d) {
        for (var i in arguments) {
            document.write(i+"<br />")
        }
        document.write(arguments.length+"個");
    };
    test2();
    // 什麼也沒有
    // 0個

腦子裏有這副圖後,咱們在來看另外一個栗子:


function test() {
        console.log(foo());
        console.log(bar);
        
        var foo = "Hello";
        console.log(foo);
        var bar = function () {
            return "world";
        };
        function foo() {
            return "hello";
        };
    };
    test();
    // "hello"
    // "undefined"
    // "Hello"

主要討論同變量聲明與同變量聲明與函數聲明的狀況,這也是變量對象在建立時所作的工做;

檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名創建一個屬性,屬性值爲指向該函數所在內存地址的引用。若是函數名的屬性已經存在,那麼該屬性將會被新的引用所覆蓋。

檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名創建一個屬性,屬性值爲undefined。若是該變量名的屬性已經存在,爲了防止同名的函數被修改成undefined,則會直接跳過,原屬性值不會被修改。

  • 如代碼中的foo,在test函數的執行上下文建立變量對象後,建立階段,foo就是變量對象中的的鍵名(Key),而鍵值就是函數的地址指針,由於函數聲明的優先級大於變量聲明,因此foo此時就持有了函數的引用,而var foo的變量聲明就被跳過。

  • 執行階段,進行賦值操做,foo被從新賦值"Hello",同時變量bar從未賦值(undefined)到持有一個函數的引用,這就是變量對象=>活動對象,執行棧,執行上下文所發生的操做。


關於全局上下文

var globla = 10;
    function test() {
        return globla++;
    };
    test();


// 以瀏覽器中爲例,全局對象爲window
// 全局上下文
windowEC = {
    VO: window,
    scopeChain: {},
    this: window
}

以瀏覽器中爲例,全局對象爲window
全局上下文有一個特殊的地方,它的變量對象,就是window對象。而這個特殊,在this指向上也一樣適用,this也是指向window。除此以外,全局上下文的生命週期,與程序的生命週期一致,只要程序運行不結束,好比關掉瀏覽器窗口,全局上下文就會一直存在。其餘全部的上下文環境,都能直接訪問全局上下文的屬性。

相關文章
相關標籤/搜索