本期的主題是呼叫堆疊,本計劃一共28期,每期重點攻克一個面試重難點,如果你還不瞭解本進階計劃,文末點選檢視全部文章。
如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。
執行上下文是當前 JavaScript 程式碼被解析和執行時所在環境的抽象概念。
執行上下文的型別
執行上下文總共有三種型別
-
全域性執行上下文:只有一個,瀏覽器中的全域性物件就是 window 物件,
this
指向這個全域性物件。 -
函式執行上下文:存在無數個,只有在函式被呼叫的時候才會被建立,每次呼叫函式都會建立一個新的執行上下文。
-
Eval 函式執行上下文: 指的是執行在
eval
函式中的程式碼,很少用而且不建議使用。
執行棧
執行棧,也叫呼叫棧,具有 LIFO(後進先出)結構,用於儲存在程式碼執行期間建立的所有執行上下文。
首次執行JS程式碼時,會建立一個全域性執行上下文並Push到當前的執行棧中。每當發生函式呼叫,引擎都會為該函式建立一個新的函式執行上下文並Push到當前執行棧的棧頂。
根據執行棧LIFO規則,當棧頂函式執行完成後,其對應的函式執行上下文將會從執行棧中Pop出,上下文控制權將移到當前執行棧的下一個執行上下文。
var a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
複製程式碼
執行上下文的建立
執行上下文分兩個階段建立:1)建立階段; 2)執行階段
建立階段
-
1、確定 this 的值,也被稱為 This Binding。
-
2、LexicalEnvironment(詞法環境) 元件被建立。
-
3、VariableEnvironment(變數環境) 元件被建立。
直接看虛擬碼可能更加直觀
ExecutionContext = {
ThisBinding = <this value>, // 確定this
LexicalEnvironment = { ... }, // 詞法環境
VariableEnvironment = { ... }, // 變數環境
}
複製程式碼
This Binding
-
全域性執行上下文中,
this
的值指向全域性物件,在瀏覽器中this
的值指向window
物件,而在nodejs
中指向這個檔案的module
物件。 -
函式執行上下文中,
this
的值取決於函式的呼叫方式。具體有:預設繫結、隱式繫結、顯式繫結(硬繫結)、new
繫結、箭頭函式,具體內容會在【this全面解析】部分詳解。
詞法環境(Lexical Environment)
詞法環境有兩個組成部分
-
1、環境記錄:儲存變數和函式宣告的實際位置
-
2、對外部環境的引用:可以訪問其外部詞法環境
詞法環境有兩種型別
-
1、全域性環境:是一個沒有外部環境的詞法環境,其外部環境引用為 null。擁有一個全域性物件(window 物件)及其關聯的方法和屬性(例如陣列方法)以及任何使用者自定義的全域性變數,
this
的值指向這個全域性物件。 -
2、函式環境:使用者在函式中定義的變數被儲存在環境記錄中,包含了
arguments
物件。對外部環境的引用可以是全域性環境,也可以是包含內部函式的外部函式環境。
直接看虛擬碼可能更加直觀
GlobalExectionContext = { // 全域性執行上下文
LexicalEnvironment: { // 詞法環境
EnvironmentRecord: { // 環境記錄
Type: "Object", // 全域性環境
// 識別符號繫結在這裡
outer: <null> // 對外部環境的引用
}
}
FunctionExectionContext = { // 函式執行上下文
LexicalEnvironment: { // 詞法環境
EnvironmentRecord: { // 環境記錄
Type: "Declarative", // 函式環境
// 識別符號繫結在這裡 // 對外部環境的引用
outer: <Global or outer function environment reference>
}
}
複製程式碼
變數環境
變數環境也是一個詞法環境,因此它具有上面定義的詞法環境的所有屬性。
在 ES6 中,詞法 環境和 變數 環境的區別在於前者用於儲存**函式宣告和變數( let
和 const
)繫結,而後者僅用於儲存變數( var
)**繫結。
使用例子進行介紹
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
複製程式碼
執行上下文如下所示
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 識別符號繫結在這裡
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 識別符號繫結在這裡
c: undefined,
}
outer: <null>
}
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 識別符號繫結在這裡
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 識別符號繫結在這裡
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
複製程式碼
變數提升的原因:在建立階段,函式宣告儲存在環境中,而變數會被設定為 undefined
(在 var
的情況下)或保持未初始化(在 let
和 const
的情況下)。所以這就是為什麼可以在宣告之前訪問 var
定義的變數(儘管是 undefined
),但如果在宣告之前訪問 let
和 const
定義的變數就會提示引用錯誤的原因。這就是所謂的變數提升。
執行階段
此階段,完成對所有變數的分配,最後執行程式碼。
如果 Javascript 引擎在原始碼中宣告的實際位置找不到 let
變數的值,那麼將為其分配 undefined
值。
參考
進階系列目錄
- 【進階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。
我是木易楊,網易高階前端工程師,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!