在這篇文章中,將深刻研究JavaScript最基本的部分之一,即執行上下文。在這篇文章的最後,你應該更清楚地理解解釋器要作什麼,爲何在聲明一些函數/變量以前可使用它們,以及它們的值是如何肯定的。web
當JavaScript代碼運行時,執行代碼的環境是至關重要的。通常有如下三種狀況:瀏覽器
把執行上下文看做是當前代碼正在執行的環境/做用域函數
// global context
var sayHello = 'sayHello'
function person() {
var first = 'webb'
var last = 'wang'
function firstName() {
return first
}
function lastName() {
return last
}
console.log(sayHello + firstName() + '' + lastName())
}
複製代碼
以上代碼沒什麼特別的地方,它包括1個全局上下文和3個不一樣的函數上下文,全局上下文能夠被程序中的其它任何上下文訪問。ui
你能夠有任意數量的函數上下文,每一個函數被調用的時候都會建立一個新的上下文。每一個下文都有一個不能被外部函數直接訪問到的內部變量的私有做用域。在上面代碼的例子中,一個函數能夠訪問當前上下文外部聲明的變量,可是一個外部上下文不能夠訪問函數內部聲明的變量。this
瀏覽器中的JavaScript解釋器是做爲一個單線程實現的,這實際上意味着,在瀏覽器中,一次只能發生一件事,其餘操做或事件將排隊在所謂的執行堆棧中。spa
當瀏覽器開始執行腳本時,首先會默認進入全局執行上下文,若是在全局代碼中調用了函數,程序會按照順序進入被調用函數,建立一個新的執行上下文,並推入到執行棧的棧頂。線程
若是你在當前執行的函數中,調用了另外的函數,代碼的執行流將會進入函數內部,並建立一個新的執行上下文推入到執行棧頂。瀏覽器老是會先執行棧頂的代碼,而且一旦函數完成執行當前執行上下文,他就會從棧頂彈出,將控制權返回到當前堆棧中的上下文。指針
關於執行堆棧有如下關鍵點code
如今咱們知道每當有函數被調用時,都會建立一個新的執行上下文。在js內部,每一個執行上文建立都要經歷下面2個階段對象
1.建立階段(函數被調用,但尚未執行內部代碼)
2.代碼執行階段
能夠將每一個執行上下文概念上表示爲一個具備3個屬性的對象:
executionContextObj = {
'scopeChain': { /* variableObject + all parent execution context's variableObject */ },
'variableObject': { /* function arguments / parameters, inner variable and function declarations */ },
'this': {}
}
複製代碼
當函數被調用時,在建立階段解釋器會建立包含有函數內部變量,參數的一個變量對象
你能夠在網上找到許多用JavaScript定義術語提高的資源,解釋變量和函數聲明被提高到函數做用域的頂部。可是,沒有人詳細解釋爲何會發生這種狀況,並且有了解釋器如何建立激活對象的新知識,就很容易理解爲何會發生這種狀況。如下面的代碼爲例:
(function() {
console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined
var foo = 'hello',
bar = function() {
return 'world';
};
function foo() {
return 'hello';
}
}());
複製代碼
若是咱們遵循建立階段,咱們就知道在代碼執行階段以前已經建立了變量。所以,當函數流開始執行時,foo已經在活動對象中定義。
儘管foo聲明瞭兩次,但從建立階段咱們就知道函數是在變量以前在變量對象上建立的,若是變量對象上的屬性名已經存在,那麼咱們只需繞過。 所以,首先在變量對象上建立對函數foo()的引用,當解釋器到達var foo時,咱們已經看到了屬性名foo的存在,因此代碼什麼也不作,繼續執行
bar其實是一個具備函數賦值的變量,咱們知道這些變量是在建立階段建立的,可是它們是用undefined值初始化的。
但願如今你已經很好地理解了JavaScript解釋器是如何執行代碼的。理解執行上下文和堆棧可讓你瞭解代碼沒有按照預期執行的緣由