JavaScript深刻系列第三篇,講解執行上下文棧的是如何執行的,也回答了第二篇中的略難的思考題。javascript
若是要問到 JavaScript 代碼執行順序的話,想必寫過 JavaScript 的開發者都會有個直觀的印象,那就是順序執行,畢竟:java
var foo = function () { console.log('foo1'); } foo(); // foo1 var foo = function () { console.log('foo2'); } foo(); // foo2
然而去看這段代碼:git
function foo() { console.log('foo1'); } foo(); // foo2 function foo() { console.log('foo2'); } foo(); // foo2
打印的結果倒是兩個 foo2
。github
刷過面試題的都知道這是由於 JavaScript 引擎並不是一行一行地分析和執行程序,而是一段一段地分析執行。當執行一段代碼的時候,會進行一個「準備工做」,好比第一個例子中的變量提高,和第二個例子中的函數提高。面試
可是本文真正想讓你們思考的是:這個「一段一段」中的「段」到底是怎麼劃分的呢?數組
到底JavaScript引擎遇到一段怎樣的代碼時纔會作「準備工做」呢?閉包
這就要說到 JavaScript 的可執行代碼(executable code)的類型有哪些了?app
其實很簡單,就三種,全局代碼、函數代碼、eval代碼。函數
舉個例子,當執行到一個函數的時候,就會進行準備工做,這裏的「準備工做」,讓咱們用個更專業一點的說法,就叫作"執行上下文(execution contexts)"。this
接下來問題來了,咱們寫的函數多了去了,如何管理建立的那麼多執行上下文呢?
因此 JavaScript 引擎建立了執行上下文棧(Execution context stack,ECS)來管理執行上下文
爲了模擬執行上下文棧的行爲,讓咱們定義執行上下文棧是一個數組:
ECStack = [];
試想當 JavaScript 開始要解釋執行代碼的時候,最早遇到的就是全局代碼,因此初始化的時候首先就會向執行上下文棧壓入一個全局執行上下文,咱們用 globalContext 表示它,而且只有當整個應用程序結束的時候,ECStack 纔會被清空,因此 ECStack 最底部永遠有個 globalContext:
ECStack = [ globalContext ];
如今 JavaScript 遇到下面的這段代碼了:
function fun3() { console.log('fun3') } function fun2() { fun3(); } function fun1() { fun2(); } fun1();
當執行一個函數的時候,就會建立一個執行上下文,而且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。知道了這樣的工做原理,讓咱們來看看如何處理上面這段代碼:
// 僞代碼 // fun1() ECStack.push(<fun1> functionContext); // fun1中居然調用了fun2,還要建立fun2的執行上下文 ECStack.push(<fun2> functionContext); // 擦,fun2還調用了fun3! ECStack.push(<fun3> functionContext); // fun3執行完畢 ECStack.pop(); // fun2執行完畢 ECStack.pop(); // fun1執行完畢 ECStack.pop(); // javascript接着執行下面的代碼,可是ECStack底層永遠有個globalContext
好啦,如今咱們已經瞭解了執行上下文棧是如何處理執行上下文的,因此讓咱們看看上篇文章《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();
是否是有些不一樣呢?
固然了,這樣歸納的回答執行上下文棧的變化不一樣,是否是依然有一種意猶未盡的感受呢,爲了更詳細講解兩個函數執行上的區別,咱們須要探究一下執行上下文到底包含了哪些內容,因此歡迎閱讀下一篇《JavaScript深刻之變量對象》。
JavaScript深刻系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript深刻系列預計寫十五篇左右,旨在幫你們捋順JavaScript底層知識,重點講解如原型、做用域、執行上下文、變量對象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎star,對做者也是一種鼓勵。