JavaScript之變數物件

南波發表於2018-10-17

概述

JavaScript 的可執行程式碼,具有執行上下文,而每個上下文包括以下 3 個屬性:

  1. 變數物件(variable object, 簡稱 VO)
  2. 作用域鏈(scope chain)
  3. this

變數物件提供了當前環境所需的變數和函式
作用域鏈用於保證 JS 中變數和函式有序地訪問
this 為函式提供了執行者物件

一個上下文的執行週期可以用下圖示意:

JavaScript之變數物件

本文就來介紹執行上下文中的變數物件

那什麼是變數物件呢?先看定義:

變數物件是與執行上下文相關的資料作用域,用於儲存執行上下文中的變數和函式宣告。

不同的執行上下文,變數物件會有一些差別。接下來就分別針對不同的上下文討論其區別。

一、全域性上下文

全域性物件(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 (字串型別的整數) 屬性的值就是函式的引數值(按引數列表從左到右排列)

三、建立過程

我們再一次來看這個過程圖:

JavaScript之變數物件

建立階段

全域性物件初始化的時候,就將變數物件引用了自身。 而函式的建立卻有需要注意的地方。

函式在建立階段就建立了變數物件
其中,變數物件包括:

  1. 當前函式的引數列表,建立 Arguments 物件。
  2. 所有的函式宣告(不包括函式表示式哦!),直接指向函式
  3. 所有的變數宣告(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, 函式宣告,變數宣告。在函式建立階段,變數物件有預設值,進入執行階段後,變數物件會被啟用成活動物件,然後變數物件的值被順序改變。

歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。

JavaScript之變數物件

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply

相關文章