執行環境(Execution Context,也稱為"執行上下文")是JavaScript中最為重要的一個概念。執行環境定義了變數或函數有權訪問的其它數據,決定了各自的行為。當JavaScript代碼執行的時候,會進入不同的執行環境,這些不同的執行環境就構成了執行環境棧。 JavaScript中主要 ...
執行環境(Execution Context,也稱為"執行上下文")是JavaScript中最為重要的一個概念。執行環境定義了變數或函數有權訪問的其它數據,決定了各自的行為。當JavaScript代碼執行的時候,會進入不同的執行環境,這些不同的執行環境就構成了執行環境棧。
JavaScript中主要存在三種執行環境:
- 全局執行環境
JavaScript代碼執行的預設環境。通常被預設為window對象,所有的全局變數和函數都作為window對象的屬性和方法存在。當執行環境中的代碼執行完畢之後,執行環境被銷毀,其中的所有變數和函數也隨之銷毀。對於全局執行環境來說,當關閉網頁或瀏覽器時,該環境被銷毀。
- 函數執行環境
當執行一個JavaScript函數時,函數的環境被推入環境棧中,執行完畢之後,棧將執行環境推出,將控制權轉交給之前的執行環境。
- Eval環境
執行eval()函數時創建。
對於執行環境棧,請看如下代碼:
var a = "global"; function example(){ console.log(a); } function outer(){ var b = "outer"; console.log(b); function inner(){ var c = "inner"; console.log(c); example(); } inner(); } outer();
代碼首先進入全局執行環境,然後依次進入outer,inner和example的執行環境,執行環境棧可以表示為:
每個執行環境都有三個重要的屬性,變數對象(VO)、作用域鏈(scope chain)和this。下麵首先看一下變數對象。
變數對象和活動對象(VO和AO)
變數對象
每個執行環境都有一個與之關聯的變數對象(variable object),環境中定義的所有變數和函數都保存在這個對象中。當代碼在一個環境中執行時,會創建當前變數對象的一個作用域鏈(scope chain)。作用域鏈的最前端,始終是當前執行環境的變數對象。如果執行環境是函數,則其活動對象(activation object)作為變數對象。作用域鏈的下一個變數對象來自於父執行環境,而再下一個變數對象來自於父環境的外部環境,以此類推構成完整的作用域鏈,而最外層的變數對象始終是全局執行環境的變數對象。
一般來說,變數對象(VO)中包含以下信息:
- 變數
- 函數聲明
- 函數的形參
當JavaScript代碼執行的時候,如果試圖尋找一個變數或函數,就會首先尋找VO。對於前面提到的代碼,全局執行環境的VO如下所示:
對於VO來說,函數表達式不包含在VO中,沒有使用var聲明的變數也不包含在VO中,這種方式只是給Global添加了一個屬性。
活動對象
只有全局執行環境的變數對象允許通過VO的屬性名稱間接訪問。但在函數執行環境中,VO是不允許被直接訪問的。此時,由活動對象(Activation Object,簡稱AO)扮演VO的角色。活動對象在進入函數執行環境時被創建,它通過函數的arguments屬性初始化,其中Arguments Objects是函數執行環境中活動對象AO的內部對象。
VO和AO的關係,簡單點說就是,VO在不同的執行環境中有不同的變現形式。在全局執行環境中,可以直接使用VO;但是在函數執行環境中,AO被創建。
在上面的代碼例子中,當開始執行outer函數的時候,outer函數的AO被創建如下圖所示:
執行環境的具體過程
當進入一個執行環境的時候,JavaScript解釋器會創建新的執行環境,但具體是怎麼做的呢?主要分為兩個階段:
- 創建階段
- 創建作用域鏈
- 創建VO/AO
- 設置this的值
- 執行階段
- 設置變數的值
- 設置函數的引用
- 解釋執行代碼
對於"創建VO/AO"這一步,JavaScript解釋器主要做了下麵的事情:
- 根據函數參數,創建並初始化arguments object
- 根據函數內部代碼查找函數聲明
- 對於找到的所有函數聲明,將函數名和引用全部存入VO/AO
- 如果存在同名函數,進行覆蓋
- 根據函數內部代碼查找變數聲明
- 對於找到的所有變數聲明,全部存入VO/AO,並初始化為"undefined"
- 如果變數名稱和已經聲明的形參或函數相同,那麼變數聲明不會幹擾這類屬性
看下麵的例子:
function example(p) { var a = 'hello'; var b = function b() {...}; function c() {...} } example(2);
對於上面的例子,在執行環境創建階段,會得到如下的執行環境對象:
exampleExecutionContext={ scopeChain: {...}, VO:{ arguments:{ 0:2, length:1 } p:2, c:pointer to function c() a:undefined, b:undefined }, this: {...} }
在代碼執行階段,環境對象會被更新,如下所示:
exampleExecutionContext={ scopeChain: {...}, VO:{ arguments:{ 0:2, length:1 } p:2, c:pointer to function c() a:'hello', b:pointer to function b() }, this: {...} }