通常執行順序很顯然按照建立順序執行,對大對數開發者來講並不陌生。javascript
像是這樣:java
foo(); // 報錯
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
複製代碼
然而有的時候會是這樣:數組
foo(); // foo2
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
複製代碼
咱們能夠看到,做爲函數調用的時候,會出現三個foo2。其實 JavaScript 引擎並不是一行一行
地分析和執行程序,在執行以前會對一些對結構進行分析執行。好比第一個例子中的變量提高,和第二個例子中的函數提高。 那麼JS又是如何進行結構化的解析的呢?執行過程當中又作了什麼動做呢?bash
棧又是什麼?異步
這張圖分別展現了棧、堆和隊列,其中棧就是咱們所說的執行上下文棧
;堆是用於存儲複雜類型
好比:對象,數組等等。隊列就是異步隊列,用於事件循環(event loop)
的執行。 JS代碼在引擎中是以代碼片斷的方式來分析執行的,而並不是一行一行來分析執行。函數
簡單的例子oop
//入棧過程
Stack.push("chris")
Stack.push("james")
Stack.push("kobe")
//出棧過程
Stack.pop() //["chirs","james"]
Strack.pop() //["chirs"]
Stack.pop() //[]
複製代碼
能夠看出,棧是執行過程是一個先入後出的過程。post
而這些代碼片斷的可執行代碼無非爲三種:全局代碼(Global code)
、函數代碼(Function Code)
、eval代碼(Eval code)
。 這些可執行代碼在執行的時候又會建立一個一個的執行上下文(Execution context)。例如,當執行到一個 函數的時候,JS引擎會作一些「準備工做」,而這個「準備工做」,咱們稱其爲執行上下文
。 那麼隨着咱們的執行上下文數量的增長,JS引擎又如何去管理這些執行上下文呢?這時便有了執行上下文棧
。ui
問題來了,咱們寫的函數多了去了,如何管理建立的那麼多執行上下文呢?spa
因此 JavaScript 引擎建立了執行上下文棧(Execution context stack,ECS)來管理執行上下文
爲了模擬執行上下文棧的行爲,讓咱們定義執行上下文棧是一個數組:
Stack= [];
複製代碼
試想當 JavaScript 開始要解釋執行代碼的時候,最早遇到的就是全局代碼,因此初始化的時候首先就會向執
行上下文棧壓入一個全局執行上下文,咱們用 globalContext 表示它,而且只有當整個應用程序結束的時候,
Stack纔會被清空,因此程序結束以前, Stack最底部永遠有個 globalContext:
Stack = [
globalContext// 一開始只有全局上下文
];
複製代碼
一個簡單的例子:
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
複製代碼
每當一個函數執行,就會建立一個執行上下文,而且壓入執行上下文棧,當函數執行完畢的時候,就會將函
數的執行上下文從棧中彈出。
// fun1()
Stack .push(<fun1> functionContext);
//[globalContext,<fun1> functionContext]
// fun1中有fun2,還要建立fun2的執行上下文
Stack .push(<fun2> functionCofuntext);
//[globalContext,<fun1> functionContext,<fun2> functionCofuntext],
// fun2還調用了fun3
Stack .push(<fun3> functionContext);
//[globalContext,<fun1> functionContext,<fun2> functionCofuntext,<fun3> functionContext]
// fun3執行完畢
Stack .pop();
//[globalContext,<fun1> functionContext,<fun2> functionCofuntext]
// fun2執行完畢
Stack .pop();
//[globalContext,<fun1> functionContext]
// fun1執行完畢
Stack .pop();
//[globalContext]
// javascript接着執行下面的代碼,可是Stack 底層永遠有個globalContext
複製代碼
一個複雜的例子:
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()();
複製代碼
兩段代碼執行的結果同樣,可是兩段代碼究竟有哪些不一樣呢?
答案就是執行上下文棧的變化不同。
先模擬第一段代碼:
Stack.push(<checkscope> functionContext);
Stack.push(<f> functionContext);
Stack.pop();
Stack.pop();
複製代碼
再模擬第二段代碼:
Stack.push(<checkscope> functionContext);
Stack.pop();
Stack.push(<f> functionContext);
Stack.pop();
複製代碼
咱們能夠發現二三部分出棧和入棧是不一樣的。
一段一段
的方式來分析執行的,而並不是一行一行
來分析執行全局代碼(Global code)
、函數代碼(Function Code)
、eval代碼(Eval code)
,其中全局代碼
、函數代碼
比較常見,關於eval代碼
可參考JavaScript 爲何不推薦使用 eval?函數執行
的時候,就會建立一個執行上下文
,執行上下文
會進入執行上下文棧
中全局執行上下文
JavaScript基礎專題系列目錄地址:
新手寫做,若是有錯誤或者不嚴謹的地方,請大夥給予指正。若是這篇文章對你有所幫助或者有所啓發,還請給一個贊,鼓勵一下做者,在此謝過。