原文章資料:Understanding Execution Context and Execution Stak in Javascript
什么是 Excution Context
簡單的說,執(zhí)行上下文是 Javascript 代碼計算和被執(zhí)行環(huán)境的抽象概念。Javascript 代碼在哪里執(zhí)行,哪里就是執(zhí)行上下文。
Excution Context 的種類
- 全局執(zhí)行上下文 —— 最基礎(chǔ)的執(zhí)行上下文,一個運(yùn)行的 Javascript 程序只存在一個全局執(zhí)行上下文。寫在函數(shù)體之外的代碼都處在全局執(zhí)行上下文。他做了兩件事情:創(chuàng)建了一個全局對象(瀏覽器中是window對象,node環(huán)境中是global),并把全局對象賦值給 this。
- 函數(shù)執(zhí)行上下文 —— 每當(dāng)函數(shù)被調(diào)用,函數(shù)內(nèi)部就會生成一個新的執(zhí)行上下文。每個函數(shù)都有自己的執(zhí)行上下文,但是只有當(dāng)函數(shù)被調(diào)用的時候,才會被創(chuàng)建。
-
eval函數(shù)執(zhí)行上下文 ——eval函數(shù)中的代碼也有自己的執(zhí)行上下文,但是eval并不常用。
Excution Stack
執(zhí)行棧,又稱“調(diào)用?!?,是一個先進(jìn)后出的結(jié)構(gòu)(LIFO, Last in First Out),用來存放代碼執(zhí)行過程中所有的執(zhí)行上下文。
Javascript 引擎在執(zhí)行代碼時,創(chuàng)建并把全局執(zhí)行上下文壓入 Excution Stack;每當(dāng)執(zhí)行到函數(shù)調(diào)用的時候,就會創(chuàng)建新的函數(shù)執(zhí)行上下文,然后從棧頂端推入棧中。
Javascript 引擎從頂端開始執(zhí)行函數(shù),當(dāng)函數(shù)被執(zhí)行完成后,它的執(zhí)行上下文就會被推出棧,然后再去執(zhí)行棧中的下一個上下文。
?? 簡單的例子:
let 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');

執(zhí)行上下文是如何創(chuàng)建的
創(chuàng)建上下文分為兩步:
- 創(chuàng)建階段
- 執(zhí)行階段
創(chuàng)建階段:
LexicalEnvironment:詞法環(huán)境
VariableEnvironment:變量環(huán)境
- 創(chuàng)建詞法環(huán)境組件
- 創(chuàng)建變量環(huán)境組件
Lexical Environment
A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.
詞法環(huán)境是用來定義 基于ECMAScript代碼詞法嵌套結(jié)構(gòu)的具體變量和函數(shù)的關(guān)聯(lián) 的具體類型。詞法環(huán)境由環(huán)境記錄器和一個只想外部詞法環(huán)境的 引用(outer) 組成。
每一個 詞法環(huán)境 由以下結(jié)構(gòu)組成:
- Environment Record
- Reference to the outer environment
- This binding
Environment Record
存儲詞法環(huán)境中 定義的變量和函數(shù)。
- 聲明式環(huán)境記錄器存儲變量、函數(shù)和參數(shù)。
- 對象環(huán)境記錄器用來定義出現(xiàn)在全局上下文中的變量和函數(shù),并且存儲了全局對象包含的方法和屬性。
對于 函數(shù)內(nèi)部的詞法環(huán)境,環(huán)境記錄器還會記錄一個 arguments 對象(存儲參數(shù)的索引和值的鍵值對)和參數(shù)的長度。這個結(jié)構(gòu)看上去如何下:
function foo(a, b) { var c = a + b }
// argument object
Arguments: {0: 2, 1: 3, length: 2}
Reference to the outer environment
外部環(huán)境的引用意味著它可以訪問其父級詞法環(huán)境(作用域)
this 的綁定
在全局執(zhí)行上下文中,this 的值指向全局對象。(在瀏覽器中,this引用 Window 對象)。
在函數(shù)執(zhí)行上下文中,this 的值取決于該函數(shù)是如何被調(diào)用的。如果它被一個引用對象調(diào)用,那么 this 會被設(shè)置成那個對象,否則 this 的值被設(shè)置為全局對象或者 undefined(在嚴(yán)格模式下)。例如:
const person = {
name: 'peter',
birthYear: 1994,
calcAge: function() {
console.log(2018 - this.birthYear);
}
}
person.calcAge();
// 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
const calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given
抽象來說,詞法環(huán)境的結(jié)構(gòu)類似下面代碼表示:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
}
outer: <null>,
this: <global object>
}
}
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
},
outer: <Global or outer function environment reference>,
this: <depends on how function is called>
}
}
Variable Environment
它同樣是一個詞法環(huán)境,其環(huán)境記錄器持有變量聲明語句在執(zhí)行上下文中創(chuàng)建的綁定關(guān)系。它有著上面定義的詞法環(huán)境的所有屬性。
在 ES6 中,詞法環(huán)境組件和變量環(huán)境的一個不同就是前者被用來存儲函數(shù)聲明和變量(let 和 const)綁定,而后者只用來存儲 var 變量綁定。
執(zhí)行階段
在此階段,將完成對所有這些變量的分配,并最終執(zhí)行代碼。
例子??
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
當(dāng)上面的代碼執(zhí)行時,Javascript引擎會創(chuàng)建一個全局執(zhí)行上下文來執(zhí)行全局代碼。全局上下文在創(chuàng)建階段看上去和這個差不多:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}
在變量分配在執(zhí)行階段完成,全局執(zhí)行上下文如下:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: 20,
b: 30,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}
}
當(dāng)執(zhí)行函數(shù) multiply(20, 30),就會創(chuàng)建一個新的函數(shù)執(zhí)行上下文來執(zhí)行函數(shù)代碼:
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: undefined
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}
}
之后執(zhí)行上下文來到 執(zhí)行階段,這意味著已完成對函數(shù)內(nèi)部變量的分配。
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: 20
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}
}
函數(shù)執(zhí)行之后,返回值存到了變量 c 中,隨后全局詞法環(huán)境被更新了,全局的代碼運(yùn)行完成,程序結(jié)束。
上面我們看到 let 和 const 聲明的變量并沒有被初始化,但是 var 定義的變量被設(shè)置為 undefined(這對應(yīng)了 let 和 const 沒有變量提示這一特性)。
這是因為在創(chuàng)建階段,Javascript 引擎讀取了所有的代碼,找到了變量和函數(shù)的定義,其中函數(shù)聲明被完整的存在了環(huán)境中,而變量被初始化稱 undefined (如果是 var 聲明的變量)或者保持未初始化(如果是 let 和 const 聲明的變量)。
這就是為什么 var 聲明的變量可以在聲明之前被訪問到,而 let 和 const 聲明的變量不允許被訪問(reference error)。
這就是常說的 變量提升(varaible hoisting) 。