JavaScript深入之執行上下文棧
順序執行?
如果要問到JavaScript代碼執行順序的話,想必寫過JavaScript的開發者都會有個直觀的印象,那就是順序執行,畢竟
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2</code></pre>
然而去看這段代碼:
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2</code></pre>
打印的結果卻是兩個foo2。
刷過面試題的都知道這是因為JavaScript引擎并非一行一行地分析和執行程序,而是一段一段地分析執行。當執行一段代碼的時候,會進行一個“準備工作”,比如第一個例子中的變量提升,和第二個例子中的函數提升。
但是本文真正想讓大家思考的是:這個"一段一段"中的“段”究竟是怎么劃分的呢?
到底JavaScript引擎遇到一段怎樣的代碼時才會做'準備工作'呢?
可執行代碼
這就要說到JavaScript的可執行代碼(executable code)的類型有哪些了?
其實很簡單,就三種,全局代碼、函數代碼、eval代碼。
舉個例子,當執行到一個函數的時候,就會進行準備工作,這里的'準備工作',讓我們用個更專業一點的說法,就叫做"執行上下文(execution contexts)"。
執行上下文棧
接下來問題來了,我們寫的函數多了去了,如何管理創建的那么多執行上下文呢?
所以js引擎創建了執行上下文棧(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();</code></pre>
當遇到一個函數代碼的時候,就會創建一個執行上下文,并且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。知道了這樣的工作原理,讓我們來看看如何處理上面這段代碼:
// 偽代碼
// 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</code></pre>
解答思考題
好啦,到此為止,我們已經了解了執行上下文棧如何處理執行上下文的,所以讓我們看看 《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深入之變量對象》
來自:https://segmentfault.com/a/1190000009006005