1. 什麼是執行上下文
當 JavaScript 代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context)。javascript
2. 可執行代碼(EC)的類型
- 全局代碼
- 函數代碼
- Eval代碼
3. 執行上下文堆棧
- 瀏覽器裏的JavaScript解釋器被實現爲單線程。這意味着同一時間只能發生一件事情,其餘的行文或事件將會被放在叫作執行棧裏面排隊。JavaScript 引擎建立了執行上下文棧(Execution context stack,ECS)來管理執行上下文。
- 當 JavaScript 開始要解釋執行代碼的時候,最早遇到的就是全局代碼,因此初始化的時候首先就會向執行上下文棧壓入一個全局執行上下文,咱們用 globalContext 表示它,而且只有當整個應用程序結束的時候,ECStack 纔會被清空,因此程序結束以前, ECStack 最底部永遠有個 globalContext。
- 在你的全局代碼中調用一個函數,你程序的時序將進入被調用的函數,並建立一個新的執行上下文,並將新建立的上下文壓入執行棧的頂部,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。
4. 調用執行上下文,分爲兩個階段
- 建立階段【當函數被調用,但未執行任何其內部代碼以前】:
- 建立做用域鏈(Scope Chain)
- 建立變量,函數和參數(該過程是有前後順序的:函數的形參==>>函數聲明==>>變量聲明)
- 求」this「的值
- 激活/代碼執行階段:
5. 解釋器執行代碼的僞邏輯
- 查找調用函數的代碼。
- 執行代碼以前,先進入建立上下文階段:
- 初始化做用域鏈
- 建立變量對象:
- 建立arguments對象,檢查上下文,初始化參數名稱和值並建立引用的複製。
- 掃描上下文的函數聲明(而非函數表達式):爲發現的每個函數,在變量對象上建立一個屬性——確切的說是函數的名字——其有一個指向函數在內存中的引用。若是函數的名字已經存在,引用指針將被重寫。
- 掃描上下文的變量聲明:爲發現的每一個變量聲明,在變量對象上建立一個屬性——就是變量的名字,而且將變量的值初始化爲undefined。若是變量的名字已經在變量對象裏存在,將不會進行任何操做並繼續掃描。
- 求出上下文內部「this」的值。
- 激活/代碼執行階段:在當前上下文上運行/解釋函數代碼,並隨着代碼一行行執行指派變量的值。
5. 提高(Hoisting)
(function() {
console.log(typeof foo); // 函數指針
console.log(typeof bar); // undefined
var foo = 'hello',
bar = function() {
return 'world';
};
function foo() {
return 'hello';
}
}());
複製代碼
-
爲何咱們能在foo聲明以前訪問它?java
回想在執行代碼以前,先進入建立階段,咱們知道函數在該階段就已經被建立在變量對象中。因此在函數開始執行以前,foo已經被定義了。瀏覽器
-
Foo被聲明瞭兩次,爲何foo顯示爲函數而不是undefined或字符串?bash
咱們知道,在建立階段,函數聲明是優先於變量被建立的。並且在變量的建立過程當中,若是發現變量對象VO
中已經存在相同名稱的屬性,則不會影響已經存在的屬性。所以,對foo()
函數的引用首先被建立在活動對象裏,而且當咱們解釋到var foo時,咱們看見foo
屬性名已經存在,因此代碼什麼都不作並繼續執行。函數
-
爲何bar的值是undefined?ui
bar採用的是函數表達式的方式來定義的,因此bar其實是一個變量,但變量的值是函數,而且咱們知道變量在建立階段被建立但他們被初始化爲undefined,這也是爲何函數表達式不會被提高的緣由。this
6. 總結
- 執行上下文(EC)分爲兩個階段,建立執行上下文和執行代碼。
EC
建立的過程是由前後順序的:參數聲明 > 函數聲明> 變量聲明。