執行環境和變數物件的深入理解

Macho發表於2017-09-28

執行環境(execution context,簡稱為環境)也叫執行上下文,是JavaScript中最為重要的一個概念。每當程式的執行流進入到一個可執行的程式碼時,就進入到了一個執行環境中。可執行程式碼分為三類:
    * 全域性程式碼:這種型別的程式碼是在"程式"級處理的。例如載入外部的js檔案或者本地  <script></script>標籤內的程式碼。全域性程式碼不包括任何function體內的程式碼。
    * 函式程式碼:即funtion函式程式碼
    * Eval程式碼:Eval內部的程式碼。

對應的執行環境也分為三種:
   * 全域性執行環境:這個是最外圍的程式碼執行環境,一旦程式碼被載入,引擎最先進入的就是這個環境。在瀏覽器中,全域性執行環境就是window物件,因此所有全域性屬性和函式都是作為window物件的屬性和方法建立的。全域性執行環境直到應用程式退出時才會被銷燬。
    * 函式執行環境: 當執行一個函式時,JavaScript引擎進入函式執行環境。某個執行環境中的程式碼執行完之後,該環境銷燬,儲存在其中的所有變數和函式定義也隨之銷燬。
    * Eval執行環境:當執行一個Eval函式時,JavaScript引擎進入Eval執行環境。

每個執行環境都有一個與之關聯的變數物件(variable object),環境中定義的所有變數和函式都儲存在這個物件中。執行環境是函式時,對應的變數物件叫活動物件(activation object)。當執行流進入一個函式時,函式的執行環境就會被推入環境棧中。而在函式執行完之後,棧將其環境彈出,該環境被銷燬,儲存在其中的所有變數和函式定義也隨之被銷燬,然後把控制權返回給之前的執行環境。棧的底部始終是全域性環境,頂部是當前活動的執行環境。
函式執行環境的建立分為兩個階段:
1.建立階段(發生在當呼叫一個函式時,但是在執行函式體內的具體程式碼以前),在這個階段
   * 建立變數物件,此時的變數物件是活動物件AO
   * 作用域鏈被初始化(scope chain)
   * 確定this指向的物件
2.程式碼執行階段:進行變數賦值,函式引用,以及執行其它程式碼。(此時變數名會覆蓋相同的函式名)

實際上可以把執行上下文看作物件,而變數物件,作用域鏈和this是執行上下文的屬性。
executionContextObj = {
variableObject: { /* 函式中的arguments物件, 引數, 內部的變數以及函式宣告 */ },
scopeChain: { /* variableObject 以及所有父執行上下文中的variableObject */ },
this: {}
}
對於”建立變數物件”這一步,JavaScript直譯器主要做了下面的事情:
   * 根據函式的引數,建立並初始化arguments object,然後把形參作為屬性,實參作為屬性值。(在全域性環境中不存在arguments物件,也沒有引數這幾個屬性)
    * 掃描函式內部程式碼,查詢函式宣告(Function declaration)。 對於所有找到的函式宣告,將函式名和函式引用存入VO/AO中。如果VO/AO中已經有同名的函式或變數名,那麼就進行覆蓋。
    * 掃描函式內部程式碼,查詢變數宣告(Variable declaration)。對於所有找到的變數宣告(用var宣告的變數),將變數名存入VO/AO中,並初始化為”undefined”。如果VO/AO中已經存在同名的變數,就什麼也不做,繼續掃描。
在建立階段,變數物件的屬性除了arguments,函式的宣告,以及引數被賦予了具體的屬性值,其它的變數屬性預設的都是undefined。而且函式名會覆蓋相同的變數名。
例如:
function test(x,y){
a = 10;
var b = 10;
var c = function add(){
};
console.log(x+y);
}
test(1,2);
對於上面的程式碼,在”建立階段”,可以得到下面的活動物件,其中變數a和函式add不在活動物件裡面。
AO = {
arguments:{
0:1,
1:2,
callee:function test(x,y),
length:2,
。。。。
},
x:1,
y:2,
b:undefined,
c:undefined,
test:function test(x,y)
}
對於VO/AO,是有下面兩種特殊情況的:
* 函式表示式(與函式宣告相對)不包含在VO/AO之中
* 沒有使用var宣告的變數(這種變數是,”全域性”的宣告方式,只是給Global新增了一個屬性,並不在VO/AO中)
抽象變數物件VO (變數初始化過程的一般行為)

╠══> 全域性上下文變數物件GlobalContextVO
║ (VO === this === global)

╚══> 函式上下文變數物件FunctionContextVO
(VO === AO, 並且新增了<arguments>和<formal parameters>)
全域性執行環境的建立和函式執行環境的建立基本相同,唯一不同的就是變數物件。全域性執行環境建立的變數物件不包含arguments物件,也沒有引數這個屬性。全域性執行環境建立的變數物件就是全域性物件。
全域性物件:全域性物件(Global object) 是在進入任何執行上下文之前就已經建立了的物件。這個物件只存在一份,它的屬性在程式中任何地方都可以訪問,全域性物件的生命週期終止於程式退出那一刻。全域性物件初始建立階段將Math、String、Date、parseInt作為自身屬性,等屬性初始化,同樣也可以有額外建立的其它物件作為屬性(其可以指向到全域性物件自身)。而全域性物件的window屬性就可以引用全域性物件自身(當然,並不是所有的具體實現都是這樣):
global = {
Math: <...>,
String: <...>
...
...
window: global //引用自身
};
當訪問全域性物件的屬性時通常會忽略掉字首,這是因為全域性物件(即global物件)是不能通過名稱直接訪問的。不過我們依然可以通過全域性上下文的this來訪問全域性物件,同樣也可以遞迴引用自身。例如,DOM中的window。因此,全域性上下文的變數物件(VO)就是全域性物件(Global Object)。即:VO(globalContext) === global; 在web瀏覽器中全域性物件就是Window物件。


相關文章