概述
JavaScript 的可執行程式碼,具有執行上下文,而每個上下文包括以下 3 個屬性:
- 變數物件(variable object, 簡稱 VO)
- 作用域鏈(scope chain)
- this
變數物件提供了當前環境所需的變數和函式
作用域鏈用於保證 JS 中變數和函式有序地訪問
this 為函式提供了執行者物件
一個上下文的執行週期可以用下圖示意:
本文就來介紹執行上下文中的變數物件
。
那什麼是變數物件呢?先看定義:
變數物件是與執行上下文相關的資料作用域,用於儲存執行上下文中的變數和函式宣告。
不同的執行上下文,變數物件會有一些差別。接下來就分別針對不同的上下文討論其區別。
一、全域性上下文
全域性物件(Global object) 是在進入任何執行上下文之前就已經建立了的物件; 這個物件只存在一份,它的屬性在程式中任何地方都可以訪問,全域性物件的生命週期終止於程式退出那一刻。
在全域性程式碼的上下文執行環境中,變數物件就是全域性物件,在瀏覽器中,就是 window 物件。
此時,我們可以用 this 和 self 來訪問到全域性物件,也就是它本身
console.log(this) // window
console.log(self) // window
複製程式碼
其次,全域性物件初始建立階段將 Math、String、Date、parseInt 等函式作為自身方法,還會把全域性變數作為自己的屬性。
用虛擬碼表示就是:
global = {
Math: <...>
Date: <...>
window: global // 引用自身
}
複製程式碼
二、函式上下文
我們已經知道,變數物件儲存量執行上下文中的函式宣告和變數,在函式上下文中,多了arguments(函式引數列表)
, 一個類陣列物件。
用虛擬碼來表示:
VO = {
arguments: Arguments,
variables: undefine,
functionName: <Function reference>
}
複製程式碼
函式未進入執行階段之前,變數物件中的屬性都不能訪問!但是進入執行階段之後,變數物件轉變為了活動物件(activation object)。
所以,在函式上下文中,我們將活動物件(activation object)作為變數物件,活動物件最開始只包含一個變數就是 arguments 物件(這個物件是全域性環境中沒有的)。
arguments 的屬性值 Arguments 它包括如下屬性:
- callee — 誰呼叫了本函式
- length — 真正傳遞的引數個數
- properties-indexes (字串型別的整數) 屬性的值就是函式的引數值(按引數列表從左到右排列)
三、建立過程
我們再一次來看這個過程圖:
建立階段
全域性物件初始化的時候,就將變數物件引用了自身。 而函式的建立卻有需要注意的地方。
函式在建立階段就建立了變數物件
其中,變數物件包括:
- 當前函式的引數列表,建立 Arguments 物件。
- 所有的函式宣告(不包括函式表示式哦!),直接指向函式
- 所有的變數宣告(var 宣告的變數),預設為 undefined
進入執行上下文時,函式宣告和變數宣告都會提前,這就是宣告提升,但是變數宣告的值都是undefined,而函式宣告的變數已經可以指向函式。變數宣告的優先順序最低。
看下面這段程式碼:
function foo(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}
foo(10);
複製程式碼
當進入函式 foo 時,其變數物件的表現形式為:
VO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
c: undefined,
d: <function reference to d>,
e: undefined,
}
複製程式碼
x 是函式表示式,所以不在變數物件當中,e 變數引用的值也是函式表示式,所以變數 e 本身是宣告,所以在變數物件當中。
執行階段
當前進入執行階段,變數物件啟用成活動物件,函式會順序執行程式碼,改變變數物件的值:
以上程式碼就變成:
AO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
c: 10,
d: <reference to function declaration d>,
e: <reference to Function expression to _e>,
}
複製程式碼
接下來看一段程式碼:
console.log(foo);
function foo() {
console.log("123")
}
var foo = "456";
複製程式碼
以上會列印函式,是因為:
變數優先處理函式宣告,再是變數宣告。
再看一段程式碼:
if (true) {
var a = 1;
} else {
var b = 2;
}
console.log(a); // 1
console.log(b); // undefined
複製程式碼
雖然 else 中的程式碼永遠不會被執行,但是 b 的變數宣告在執行之前就預設被設定成 undefined了。
總結
執行上下文包括三個屬性,變數物件,作用域鏈,this, 不同的執行上下文,變數物件是有區別的。
全域性上下文中,變數物件就是本身。
函式上下文中,變數物件包括:arguments, 函式宣告,變數宣告。在函式建立階段,變數物件有預設值,進入執行階段後,變數物件會被啟用成活動物件,然後變數物件的值被順序改變。
歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。