- 原文地址:Understanding Execution Context and Execution Stack in Javascript
- 原文做者:Sukhjinder Arora
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:CoolRice
- 校對者:linxuesia, CoderMing
照片來自 Unsplash 的做者 Greg Rakozyjavascript
若是你是或者想成爲一名 JavaScript 開發者,你必須知道 JavaScript 程序內部是如何執行的。理解執行上下文和執行棧對於理解其餘 JavaScript 概念(如變量聲明提高,做用域和閉包)相當重要。前端
正確理解執行上下文和執行棧的概念將使您成爲更出色的 JavaScript 開發者。vue
閒話少說,讓咱們開始吧 :)java
使用 Bit 應用所提供的組件做爲構建模塊,你就是架構師。隨時隨地和你的團隊分享、發現和開發組件,快來嘗試鮮!react
簡而言之,執行上下文是評估和執行 JavaScript 代碼的環境的抽象概念。每當 Javascript 代碼在運行的時候,它都是在執行上下文中運行。android
JavaScript 中有三種執行上下文類型。ios
this
的值等於這個全局對象。一個程序中只會有一個全局執行上下文。eval
函數內部的代碼也會有它屬於本身的執行上下文,但因爲 JavaScript 開發者並不常用 eval
,因此在這裏我不會討論它。執行棧,也就是在其它編程語言中所說的「調用棧」,是一種擁有 LIFO(後進先出)數據結構的棧,被用來存儲代碼運行時建立的全部執行上下文。git
當 JavaScript 引擎第一次遇到你的腳本時,它會建立一個全局的執行上下文而且壓入當前執行棧。每當引擎遇到一個函數調用,它會爲該函數建立一個新的執行上下文並壓入棧的頂部。github
引擎會執行那些執行上下文位於棧頂的函數。當該函數執行結束時,執行上下文從棧中彈出,控制流程到達當前棧中的下一個上下文。編程
讓咱們經過下面的代碼示例來理解:
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');
複製代碼
上述代碼的執行上下文棧。
當上述代碼在瀏覽器加載時,JavaScript 引擎建立了一個全局執行上下文並把它壓入當前執行棧。當遇到 first()
函數調用時,JavaScript 引擎爲該函數建立一個新的執行上下文並把它壓入當前執行棧的頂部。
當從 first()
函數內部調用 second()
函數時,JavaScript 引擎爲 second()
函數建立了一個新的執行上下文並把它壓入當前執行棧的頂部。當 second()
函數執行完畢,它的執行上下文會從當前棧彈出,而且控制流程到達下一個執行上下文,即 first()
函數的執行上下文。
當 first()
執行完畢,它的執行上下文從棧彈出,控制流程到達全局執行上下文。一旦全部代碼執行完畢,JavaScript 引擎從當前棧中移除全局執行上下文。
到如今,咱們已經看過 JavaScript 怎樣管理執行上下文了,如今讓咱們瞭解 JavaScript 引擎是怎樣建立執行上下文的。
建立執行上下文有兩個階段:1) 建立階段 和 2) 執行階段。
在 JavaScript 代碼執行前,執行上下文將經歷建立階段。在建立階段會發生三件事:
因此執行上下文在概念上表示以下:
ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}
複製代碼
在全局執行上下文中,this
的值指向全局對象。(在瀏覽器中,this
引用 Window 對象)。
在函數執行上下文中,this
的值取決於該函數是如何被調用的。若是它被一個引用對象調用,那麼 this
會被設置成那個對象,不然 this
的值被設置爲全局對象或者 undefined
(在嚴格模式下)。例如:
let foo = {
baz: function() {
console.log(this);
}
}
foo.baz(); // 'this' 引用 'foo', 由於 'baz' 被
// 對象 'foo' 調用
let bar = foo.baz;
bar(); // 'this' 指向全局 window 對象,由於
// 沒有指定引用對象
複製代碼
官方的 ES6 文檔把詞法環境定義爲
詞法環境是一種規範類型,基於 ECMAScript 代碼的詞法嵌套結構來定義標識符和具體變量和函數的關聯。一個詞法環境由環境記錄器和一個可能的引用外部詞法環境的空值組成。
簡單來講詞法環境是一種持有標識符—變量映射的結構。(這裏的標識符指的是變量/函數的名字,而變量是對實際對象[包含函數類型對象]或原始數據的引用)。
如今,在詞法環境的內部有兩個組件:(1) 環境記錄器和 (2) 一個外部環境的引用。
詞法環境有兩種類型:
this
的值指向全局對象。環境記錄器也有兩種類型(如上!):
簡而言之,
注意 — 對於函數環境,聲明式環境記錄器還包含了一個傳遞給函數的 arguments
對象(此對象存儲索引和參數的映射)和傳遞給函數的參數的 length。
抽象地講,詞法環境在僞代碼中看起來像這樣:
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>
}
}
複製代碼
注意 — 只有遇到調用函數 multiply
時,函數執行上下文才會被建立。
可能你已經注意到 let
和 const
定義的變量並無關聯任何值,但 var
定義的變量被設成了 undefined
。
這是由於在建立階段時,引擎檢查代碼找出變量和函數聲明,雖然函數聲明徹底存儲在環境中,可是變量最初設置爲 undefined
(var
狀況下),或者未初始化(let
和 const
狀況下)。
這就是爲何你能夠在聲明以前訪問 var
定義的變量(雖然是 undefined
),可是在聲明以前訪問 let
和 const
的變量會獲得一個引用錯誤。
這就是咱們說的變量聲明提高。
這是整篇文章中最簡單的部分。在此階段,完成對全部這些變量的分配,最後執行代碼。
注意 — 在執行階段,若是 JavaScript 引擎不能在源碼中聲明的實際位置找到 let
變量的值,它會被賦值爲 undefined
。
咱們已經討論過 JavaScript 程序內部是如何執行的。雖然要成爲一名卓越的 JavaScript 開發者並不須要學會所有這些概念,可是若是對上面概念能有不錯的理解將有助於你更輕鬆,更深刻地理解其餘概念,如變量聲明提高,做用域和閉包。
就是這樣,若是你發現這篇文章有用,請點擊 👏 按鈕並在下面自由地評論!我很樂意和你討論 😃。
Bit 使得在項目和應用中分享小型組件和模塊變得很是簡單,使您和您的團隊能夠更快地構建代碼。隨時隨地和你的團隊分享、發現和開發組件,快來嚐鮮!
2018 年你應該知道的 11 種 React UI 組件庫:2018 年 11 種擁有優秀組件的 React 組件庫,用於構建下一代應用程序 UI 界面。
在 React 中如何寫出更好的代碼:9 個在 React 中寫出更好代碼的實用小貼士:學習 Linting、propTypes、PureComponent 還有更多。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。