JS基礎之執行上下文
- 執行上下文
- 順序執行
- 可執行代碼
- 執行上下文棧
- 回顧上文
執行上下文
順序執行
寫個JavaScript的開發者都會有個直觀的印象,那就是順序執行:
var foo = function(){console.log('foo1')
}
foo(); //foo1
var foo = function(){console.log('foo2');
}
foo();//foo2
那這段呢?
function foo(){console.log('foo1');
}
foo(); // foo2
function foo(){console.log('foo2');
}
foo(); //foo2
打印的結果卻是兩個foo2
這是因為JavaScript引擎并非一行一行的分析和執行程序,而是一段一段的分析執行。當執行一段代碼的時候,會進行一個“準備工作”,那這個“一段一段”中的“段”究竟是怎么劃分的呢?
到底JavaScript引擎遇到一段怎樣的代碼時,才會做“準備工作”呢?
console.log(add2(1,2));//輸出3
function add2(a,b){return a+b;
}
console.log(add1(1,1));//報錯:add1 is not a function
var add1 = function(a,b){return a+b;
}
//用函數語句創建的函數add2,函數名稱和函數體均被提前,在聲明他之前就使用它。
//但是使用var表達式定義函數add1,只有變量聲明提前了,變量初始化代碼仍然在原來的位置,沒有辦法提前執行。
可執行代碼
這就要說到JavaScript的可執行代碼(executable code
)的類型有哪些了?
其實很簡單,就三種,全局代碼、函數代碼、eval代碼
舉個例子,當執行到一個函數的時候,就會進行準備工作,這里的“準備工作”,讓我們用更專業的說法,就叫做“執行上下文(execution context
)”。
執行上下文棧
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()
ESCtack.push(<fun1> functionContext);//入棧
//fun1中竟然調用了fun2,還要創建fun2的執行上下文
ESCtack.push(<fun2> functionContext);//入棧
//fun2還調用了fun3!
ESCtack.push(<fun3> functionContext);//入棧
//fun3執行完畢
ESCtack.pop();//出棧
//fun2執行完畢
ESCtack.pop();//出棧
//fun1執行完畢
ESCtack.pop();//出棧
//javascript接著執行下面的代碼,但是ESCtack底層永遠有個globalContext
回顧上文
//case 1
var scope = "global scope";
function checkscope(){var scope = "local scope";function f(){return scope;}return f();
}
checkscope();//case 2
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()//出棧
這就是上文說到的區別。
好啦!這期就到這里啦,歡迎友友們留言討論吶~