【進階1-2期】JavaScript深刻之執行上下文棧和變量對象

更新:謝謝你們的支持,最近折騰了一個博客官網出來,方便你們系統閱讀,後續會有更多內容和更多優化,猛戳這裏查看前端

------ 如下是正文 ------webpack

本期的主題是調用堆棧,本計劃一共28期,每期重點攻克一個面試重難點,若是你還不瞭解本進階計劃,文末點擊查看所有文章。git

若是以爲本系列不錯,歡迎點贊、評論、轉發,您的支持就是我堅持的最大動力。github


JS是單線程的語言,執行順序確定是順序執行,可是JS 引擎並非一行一行地分析和執行程序,而是一段一段地分析執行,會先進行編譯階段而後纔是執行階段。web

翠花,上代碼面試

例子一:變量提高算法

foo;  // undefined
var foo = function () {
    console.log('foo1');
}

foo();  // foo1,foo賦值

var foo = function () {
    console.log('foo2');
}

foo(); // foo2,foo從新賦值
複製代碼

例子二:函數提高跨域

foo();  // foo2
function foo() {
    console.log('foo1');
}

foo();  // foo2

function foo() {
    console.log('foo2');
}

foo(); // foo2
複製代碼

例子三:聲明優先級,函數 > 變量數組

foo();  // foo2
var foo = function() {
    console.log('foo1');
}

foo();  // foo1,foo從新賦值

function foo() {
    console.log('foo2');
}

foo(); // foo1
複製代碼

上面三個例子中,第一個例子是變量提高,第二個例子是函數提高,第三個例子是函數聲明優先級高於變量聲明。瀏覽器

須要注意的是同一做用域下存在多個同名函數聲明,後面的會替換前面的函數聲明。

執行上下文

執行上下文總共有三種類型

  • 全局執行上下文:只有一個,瀏覽器中的全局對象就是 window 對象,this 指向這個全局對象。
  • 函數執行上下文:存在無數個,只有在函數被調用的時候纔會被建立,每次調用函數都會建立一個新的執行上下文。
  • Eval 函數執行上下文: 指的是運行在 eval 函數中的代碼,不多用並且不建議使用。

這部份內容在【進階1-1期】中詳細介紹了,點擊查看【進階1-1期】理解JavaScript 中的執行上下文和執行棧

執行上下文棧

由於JS引擎建立了不少的執行上下文,因此JS引擎建立了執行上下文(Execution context stack,ECS)來管理執行上下文。

當 JavaScript 初始化的時候會向執行上下文棧壓入一個全局執行上下文,咱們用 globalContext 表示它,而且只有當整個應用程序結束的時候,執行棧纔會被清空,因此程序結束以前, 執行棧最底部永遠有個 globalContext。

ECStack = [		// 使用數組模擬棧
    globalContext
];
複製代碼

具體執行過程以下圖所示,這部份內容在【進階1-1期】中詳細介紹了,點擊查看【進階1-1期】理解JavaScript 中的執行上下文和執行棧

找不一樣

有以下兩段代碼,執行的結果是同樣的,可是兩段代碼究竟有什麼不一樣?

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
複製代碼
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
複製代碼

答案是 執行上下文棧的變化不同。

第一段代碼:

ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
複製代碼

第二段代碼:

ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
複製代碼

函數上下文

在函數上下文中,用活動對象(activation object, AO)來表示變量對象。

活動對象和變量對象的區別在於

  • 一、變量對象(VO)是規範上或者是JS引擎上實現的,並不能在JS環境中直接訪問。
  • 二、當進入到一個執行上下文後,這個變量對象纔會被激活,因此叫活動對象(AO),這時候活動對象上的各類屬性才能被訪問。

調用函數時,會爲其建立一個Arguments對象,並自動初始化局部變量arguments,指代該Arguments對象。全部做爲參數傳入的值都會成爲Arguments對象的數組元素。

執行過程

執行上下文的代碼會分紅兩個階段進行處理

  • 一、進入執行上下文

  • 二、代碼執行

進入執行上下文

很明顯,這個時候尚未執行代碼

此時的變量對象會包括(以下順序初始化):

  • 一、函數的全部形參 (only函數上下文):沒有實參,屬性值設爲undefined。
  • 二、函數聲明:若是變量對象已經存在相同名稱的屬性,則徹底替換這個屬性。
  • 三、變量聲明:若是變量名稱跟已經聲明的形參或函數相同,則變量聲明不會干擾已經存在的這類屬性。

上代碼就直觀了

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;
}

foo(1);
複製代碼

對於上面的代碼,這個時候的AO是

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}
複製代碼

形參arguments這時候已經有賦值了,可是變量仍是undefined,只是初始化的值

代碼執行

這個階段會順序執行代碼,修改變量對象的值,執行完成後AO以下

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}
複製代碼

總結以下:

  • 一、全局上下文的變量對象初始化是全局對象

  • 二、函數上下文的變量對象初始化只包括 Arguments 對象

  • 三、在進入執行上下文時會給變量對象添加形參、函數聲明、變量聲明等初始的屬性值

  • 四、在代碼執行階段,會再次修改變量對象的屬性值

參考

JavaScript深刻之執行上下文棧
JavaScript深刻之變量對象

進階系列目錄

  • 【進階1期】 調用堆棧
  • 【進階2期】 做用域閉包
  • 【進階3期】 this全面解析
  • 【進階4期】 深淺拷貝原理
  • 【進階5期】 原型Prototype
  • 【進階6期】 高階函數
  • 【進階7期】 事件機制
  • 【進階8期】 Event Loop原理
  • 【進階9期】 Promise原理
  • 【進階10期】Async/Await原理
  • 【進階11期】防抖/節流原理
  • 【進階12期】模塊化詳解
  • 【進階13期】ES6重難點
  • 【進階14期】計算機網絡概述
  • 【進階15期】瀏覽器渲染原理
  • 【進階16期】webpack配置
  • 【進階17期】webpack原理
  • 【進階18期】前端監控
  • 【進階19期】跨域和安全
  • 【進階20期】性能優化
  • 【進階21期】VirtualDom原理
  • 【進階22期】Diff算法
  • 【進階23期】MVVM雙向綁定
  • 【進階24期】Vuex原理
  • 【進階25期】Redux原理
  • 【進階26期】路由原理
  • 【進階27期】VueRouter源碼解析
  • 【進階28期】ReactRouter源碼解析

交流

進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,以爲不錯點個star。

我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!

相關文章
相關標籤/搜索