【進階1-2期】JavaScript深入之執行上下文棧和變數物件
本期的主題是 呼叫堆疊 ,本計劃一共28期, 每期重點攻克一個面試重難點 ,如果你還不瞭解本進階計劃,文末點選檢視全部文章。
如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。
JS是單執行緒的語言,執行順序肯定是順序執行,但是JS 引擎並不是一行一行地分析和執行程式,而是一段一段地分析執行,會先進行編譯階段然後才是執行階段。
翠花,上程式碼
例子一:變數提升
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/">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 )來表示變數物件。
活動物件和變數物件的區別在於
- 1、變數物件( VO )是規範上或者是JS引擎上實現的,並不能在JS環境中直接訪問。
- 2、當進入到一個執行上下文後,這個變數物件才會被 啟用 ,所以叫活動物件( AO ),這時候活動物件上的各種屬性才能被訪問。
呼叫函式時,會為其建立一個 Arguments物件 ,並自動初始化區域性變數arguments,指代該Arguments物件。所有作為引數傳入的值都會成為Arguments物件的陣列元素。
執行過程
執行上下文的程式碼會分成兩個階段進行處理
-
1、 進入 執行上下文
-
2、程式碼執行
進入執行上下文
很明顯,這個時候還沒有執行程式碼
此時的變數物件會包括(如下順序初始化):
- 1、函式的所有形參 (only函式上下文):沒有實參,屬性值設為undefined。
- 2、函式宣告:如果變數物件已經存在相同名稱的屬性,則完全 替換 這個屬性。
- 3、變數宣告:如果變數名稱跟已經宣告的形參或函式相同,則變數宣告 不會干擾 已經存在的這類屬性。
上程式碼就直觀了
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" } 複製程式碼
總結如下:
-
1、全域性上下文的變數物件初始化是全域性物件
-
2、函式上下文的變數物件初始化只包括 Arguments 物件
-
3、在進入執行上下文時會給變數物件 新增形參、函式宣告、變數宣告 等初始的屬性值
-
4、在程式碼執行階段,會再次修改變數物件的屬性值
參考
進階系列目錄
- 【進階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。
我是木易楊,網易高階前端工程師,跟著我 每週重點攻克一個前端面試重難點 。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!
