深刻學習js系列是本身階段性成長的見證,但願經過文章的形式更加嚴謹、客觀地梳理js的相關知識,也但願可以幫助更多的前端開發的朋友解決問題,期待咱們的共同進步。前端
若是以爲本系列不錯,歡迎點贊、評論、轉發,您的支持就是我堅持的最大動力。git
做爲一個JavaScript的程序開發者,若是被問到JavaScript代碼的執行順序,你腦海中是否是有一個直觀的印象 -- JavaScript 是順序執行的,可事實真的是這樣的嗎?github
讓咱們首先看兩個小例子:面試
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
複製代碼
刷過面試題目的都知道:數組
JavaScript引擎並不是一行一行地分析和執行程序,而是一段一段地分析執行,當執行一段代碼的時候,會進行一個準備工做。瀏覽器
好比咱們熟悉的JavaScript中的變量提高好比函數提高都是在這個準備階段完成的。微信
本文咱們就來深刻的研究一下,這一段一段中的段是如何劃分的呢?閉包
到底JavaScript引擎遇到一段怎樣的代碼纔會作"準備工做"呢?爲了解答這個問題咱們引入一個概念——執行上下文。ide
若是你作太小學的閱讀理解,確定見到過這樣的題目:聯繫上下文解釋句子,這裏的上下文指的多是這個句子所在的段落,也多是這個句子所在段落的臨近段落。實際上,這裏描述的是一個句子的語境和做用範圍,聯繫類比到程序中咱們能夠做以下定義:函數
執行上下文是當前JavaScript代碼被解析和執行時所在環境的抽象概念。
執行上下文總共分爲三種類型,有時候咱們也叫作可執行代碼(executable code)
window
對象,this
指向這個全局對象。Eval
函數執行上下文: 指的是運行在eval
函數中的代碼,不多用並且不建議使用。舉個例子,當執行到一個函數的時候,就會進行準備工做,這裏的"準備工做",讓咱們用個更專業一點的說法,就叫作"執行上下文(execution context)"。
接下來問題來了,咱們寫的函數多了去了,如何管理建立的那麼多執行上下文呢?因此 JavaScript 引擎建立了執行上下文棧(Execution context stack )ECStack 來管理執行上下文。
這裏咱們能夠簡單的認爲 ECStack 是一個數組,相似這樣:
ECStack = [];
複製代碼
執行棧,也叫作調用棧,具備 LIFO(last in first out 後進先出) 結構,用於存儲在代碼執行期間建立的全部執行上下文。
- 首次運行JavaScript代碼的時候,會建立一個全局執行的上下文並Push到當前的執行棧中,每當發生函數調用,引擎都會爲該函數建立一個新的函數執行上下文並Push當前執行棧的棧頂。
- 當棧頂的函數運行完成後,其對應的函數執行上下文將會從執行棧中Pop出,上下文的控制權將移動到當前執行棧的下一個執行上下文。
讓咱們看一段代碼來理解這個過程:
var a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
複製代碼
- 當上述代碼在瀏覽器加載時,JavaScript引擎建立了一個全局執行上下文並把它壓入(push) 當前的執行棧。當遇到 first() 函數調用時,JavaScript引擎爲該函數建立一個新的執行上下文並把它壓入當前執行棧的頂部。
- 當從 first() 函數內部調用 second() 函數時,JavaScript引擎爲 second() 函數建立了一個新的執行上下文並把它壓入當前執行棧的頂部,當 second() 函數執行完畢,它的執行上下文會從當前棧彈出(pop),而且控制流程到達下一個執行上下文,即 first() 函數的執行上下文。
- 當 first() 執行完畢,它的執行上下文從棧中彈出,控制流程到達了全局執行上下文。一旦全部的代碼執行完畢,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();
複製代碼
checkscope()();這裏對於這個函數的執行作一些解釋
// checkscope()() 就至關於
var f = checkscope();
f();
複製代碼
checkscope 函數執行,函數執行完畢後,該函數返回一個函數名,就至關於:
ECStack.push(<checkscope> functionContext); ECStack.pop(); 複製代碼
而後再執行的這個返回的函數,就至關於:
ECStack.push(<f> functionContext); ECStack.pop(); 複製代碼
爲了更詳細講解兩個函數執行上的區別,咱們須要探究一下執行上下文到底包含了哪些內容,咱們須要更加深刻了解變量對象的相關內容。
歡迎添加個人我的微信討論技術和個體成長。