原文做者:Valentino 原文連接:https://www.valentinog.com/blog/js-execution-context-call-stack
我打賭你不知道答案。javascript
編程語言中最基礎的組成部分是什麼?java
變量和函數對嗎?每一個人均可以學習這些板塊。編程
但除了基礎知識以外還有什麼?瀏覽器
在稱本身爲中級(甚至是高級)Javascript開發人員以前,你應該掌握的Javascript的核心是什麼?數據結構
有不少:Scope(做用域)
、Closure(閉包)
、Callbacks(回調)
、Prototype(原型)
等等。閉包
但在深刻研究這些概念以前,您至少應該瞭解Javascript引擎的工做原理。異步
在這篇文章中,咱們將介紹每一個Javascript引擎的兩個基本部分:執行上下文和調用堆棧。編程語言
(不要懼怕。它比你想象的容易)。ide
準備好了嗎?函數
目錄
在這篇文章中你將學到:
經過查看Javascript內部功能,您將成爲更好的Javascript開發人員,即便您沒法掌握每個細節。
如今,看看下面的代碼:
var num = 2; function pow(num) { return num * num; }
如今告訴我:你認爲在瀏覽器裏以何種順序執行這段代碼?
換句話說,若是您是瀏覽器,您將如何閱讀該代碼?
這聽起來很簡單。
大多數人認爲「是的,瀏覽器執行功能pow並返回結果,而後將2分配給num。」
在接下來的部分中,您將發現那些看似簡單的代碼行背後的機制。
要了解Javascript如何運行您的代碼,咱們應該遇到第一件可怕的事情:
執行上下文
在Javascript中什麼是執行上下文?
每次在瀏覽器(或Node)中運行Javascript時,引擎都會執行一系列步驟。
其中一個步驟涉及建立全局執行上下文。
什麼是引擎?
也就是說,Javascript引擎是運行Javascript代碼的「引擎」。
現在有兩個突出的Javascript引擎:Google V8和SpiderMonkey。
V8是Google開源的Javascript引擎,在Google Chrome和Nodejs中使用。
SpiderMonkey是Mozilla的JavaScript引擎,用於Firefox。
到目前爲止,咱們有Javascript引擎和執行上下文。
如今是時候瞭解它們如何協同工做了。
每次運行一些Javascript代碼是,引擎都會創造一個全局執行上下文。
執行上下文是一個比喻的詞,用於描述運行Javascript代碼的環境。
我以爲你很難想象出這些抽象的東西。
如今將全局執行上下文視爲一個框:
讓咱們再看看咱們的代碼:
var num = 2; function pow(num) { return num * num; }
引擎如何讀取該代碼?
這是一個簡化版本:
引擎:第一行,它是變量!讓咱們將它存儲在全局存儲器中。
引擎:第三行,我看到了一個函數聲明。讓咱們也把它存儲在全局存儲器中。
引擎:看起來我已經完成了。
若是我再次問你:瀏覽器如何「看到」如下代碼,你會怎麼說?
是的,它有點自上而下......
正如你所看到的那樣,引擎沒有運行功能pow!
這是一個函數聲明,而不是函數調用。
上面的代碼將轉換爲存儲在全局存儲器中的一些值:函數聲明和變量。
全局存儲器?
我已經對執行上下文感到困惑,如今還要問我什麼是全局存儲器?
接下來讓咱們看看什麼是全局存儲器
Javascript引擎也有一個全局存儲器。
全局內存包含全局變量和函數聲明供之後使用。
若是您閱讀Kyle Simpson的「做用域和閉包」,您可能會發現全局存儲器與全局做用域的概念重疊。
實際上它們是一回事。
這是些很可貴概念。
但你如今不該該擔憂。
我但願你能理解咱們難題的兩個重要部分。
當Javascript引擎運行您的代碼時,它會建立:
一切都清楚了嗎?
若是我在這一點上,我會:
您能夠在紙上或使用原型製做工具編寫練習。
對於個人小例子,圖片看起來以下:
在下一節中,咱們將看另外一個可怕的事情:調用棧。
您是否清楚地瞭解了執行上下文,全局存儲器和Javascript引擎如何組合在一塊兒?
若是沒有,花時間查看上一節。
咱們將在咱們的難題中介紹另外一篇文章:調用棧。
讓咱們首先回顧一下Javascript引擎運行代碼時會發生什麼。 它建立:
除了咱們的例子,沒有更多的事情發生:
var num = 2; function pow(num) { return num * num; }
代碼是純粹的值分配。
讓咱們更進一步。
若是我調用該函數會發生什麼?
var num = 2; function pow(num) { return num * num; } var res = pow(num)
有趣的問題。
在Javascript中調用函數的行爲使引擎尋求幫助。
這個幫助來自Javascript引擎的朋友:調用棧。
它聽起來可能並不明顯,但Javascript引擎須要跟蹤發生的狀況。
它依賴於調用棧。
什麼是Javascript中的調用棧?
調用棧就像是程序當前執行的日誌。
實際上它是一個數據結構:堆棧。
調用棧的工做原理是什麼?
不出所料,它有兩種方法:push和pop。
push是將某些東西放入堆棧的行爲。
也就是說,當您在Javascript中運行函數時,引擎會將該函數push到調用堆棧中。
每一個函數調用都被push到調用棧中。
push的第一件事是main()(或global()),它是Javascript程序執行的主要線程。
如今,上一張圖片看起來像這樣:
pop另外一端是從堆棧中刪除某些東西的行爲。
當函數執行結束時,將從調用棧中pop出去。
咱們的調用棧將以下所示:
如今?您已準備好從那裏掌握每一個Javascript概念。
請看下一部分。
到目前爲止,一切彷佛都很清楚。
咱們知道Javascript引擎建立了一個全局執行上下文和一個全局存儲器。
而後,當您在代碼中調用函數時:
當你在Javascript中運行一個函數時,還有另外一件事情發生。
首先,該功能出如今全局執行上下文中。
而後,另外一個迷你上下文出如今函數旁邊:
那個迷你上下文叫作局部執行上下文。
若是您注意到,在上一張圖片中,全局存儲器中會出現一個新變量:var res。
變量res的值首先是undefined。
而後,只要pow出如今全局執行上下文中,該函數就會執行而且res將獲取其返回值。
在執行階段,建立局部執行上下文以保存局部變量。
記住這一點。
瞭解全局和局部執行上下文是掌握做用域和閉包的關鍵。
Javascript引擎建立執行上下文,全局存儲器和調用棧。可是一旦你調用一個函數,引擎就會建立一個局部執行上下文。
常常被忽視的是,新的開發人員老是將Javascript內部視爲神祕的東西。
然而,它們是掌握高級Javascript概念的關鍵。
若是你學習執行上下文,全局存儲器和調用棧,那麼Scope,Closures,Callbacks和其餘東西將變得垂手可得。
特別是,理解調用堆棧是相當重要的。
一旦你想象它,全部的Javascript將開始有意義:你將最終理解爲何Javascript是異步的以及咱們爲何須要回調。