平常在羣裏討論一些概念性的問題,好比變量提高,做用域和閉包相關問題的時候,常常會聽一些大佬們給別人解釋的時候說執行上下文,調用上下文巴拉巴拉,總有點似懂非懂,不明覺厲的感受。今天,就對這兩個概念梳理一下,加深對js基礎核心的理解。javascript
當js引擎遇到這三種類型的代碼的時候,都會進行一些準備工做,這些準備工做,專業的說法就叫執行上下文。或者說js引擎遇到這三種類型的代碼的時候,就會進入到一個執行上下文。html
簡而言之,執行上下文是評估和執行javascript代碼的環境的抽象概念。每當javascript代碼在運行的時候,它都是在執行上下文中運行。執行上下文能夠理解爲當前代碼的執行環境,它會造成一個做用域(╭(╯^╰)╮,做用域就做用域嘛,說得這麼拗口,非要搞個什麼執行上下文的概念)。java
其實不必刻意去區分可執行代碼與執行上下文。我的理解,當別人跟你聊一些概念性的東西,聊到可執行代碼,可執行上下文,執行環境的時候,其實他們多是想說做用域,只不過表述方式不一樣罷了。數組
以前寫的這個,有點問題,慚愧。做用域與執行上下文是徹底不一樣的兩個概念。瀏覽器
JavaScript代碼的整個執行過程,分爲兩個階段,代碼編譯階段與代碼執行階段。編譯階段由編譯器完成,將代碼編譯成可執行代碼,這個階段做用域規則會肯定。執行階段由引擎完成,主要任務是執行可執行代碼,執行上下文在這個階段建立。閉包
在一個javascript程序中,一定會產生多個執行上下文,javascript引擎會以棧的方式來處理它們,也就是執行上下文棧(不少文章可能會稱它爲執行棧,執行上下文堆棧,函數調用棧,其實都是差很少的意思)。函數
關於棧的概念和特性在上一篇博客:js基礎梳理-內存空間已有介紹。this
爲了模擬執行上下文棧的行爲,能夠把它定義爲一個數組:code
ECStack = [];
如今 javascript遇到下面這段代碼了htm
let a = 'hello world'; function first () { console.log('進入 first 函數執行上下文'); second(); console.log('再次進入 first 函數執行上下文'); } function second () { console.log('進入 second 函數執行上下文'); } first(); console.log('進入 全局執行上下文(Global Execution Context)')
當上述代碼在瀏覽器加載時,Javascipt引擎建立了一個全局執行上下文並把它壓入了執行上下文棧,用 globalContext表示它,而且只有當整個應用程序結束的時候(瀏覽器關閉),ECStack纔會被清空,因此程序結束以前,ECStack最底部永遠有個 globalContext:
ECStack = [ globalContext ];
當執行到一個函數的時候,就會建立一個執行上下文,而且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。知道了這樣的工做原理,就能夠分析出 ECStack的變化過程:
// 僞代碼 // first() ECStack.push(<first> functionContext); // first中調用了second,繼續建立second的執行上下文 ECStack.push(<second> functionContext); // second執行完畢 ECStack.pop(); // first執行完畢 ECStack.pop(); // javascript接着執行下面的代碼,可是ECStack底層永遠有個globalContext;
注意:函數中,遇到return能終止可執行代碼的執行,所以會直接將當前上下文彈出棧。
例如,看如下這個閉包例子:
function f1(){ var n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999
由於f1中的函數f2在f1的可執行代碼中,並無被調用執行,所以執行f1時,f2不會建立新的上下文,而直到result執行時,才建立了一個新的。具體演變過程以下:
// 僞代碼: // 全局上下文入棧: ECStack = [ globalContext ]; // f1 EC入棧: ECStack.push(<f1> functionContext); // f1 EC出棧: ECStack.pop(); // result EC入棧: ECStack.push(<result> functionContext); // result EC出棧: ECStack.pop();
在接下來的文章中將梳理建立階段的這三個步驟。