JavaScript內功之變量對象
本篇文章:主要介紹執行上下文的組成部分之一——變量對象(VO)。本文是與系列的前幾篇文章存在一定的承接關係,大家感興趣的話不妨從頭閱讀~
目錄
前言
JavaScript編程的時候總避免不了聲明函數和變量,以成功構建我們的系統,但是解釋器是如何並且在什麼地方去查找這些函數和變量呢?我們引用這些對象的時候究竟發生了什麼?
在上篇《JavaScript中的執行上下文》中我們提到了一部分,當 JavaScript 代碼執行一段可執行代碼(executable code)時,會創建對應的執行上下文(execution context)。
對於每個執行上下文,都有三個重要屬性:
- 變量對象(Variable object,VO)
- 作用域鏈(Scope chain)
- this
一、變量對象
在函數上下文中,我們用活動對象(activation object, AO)來表示變量對象。
活動對象和變量對象其實是一個東西:
- 變量對象是規範上的或者說是引擎實現上的,不可在 JavaScript 環境中訪問
- 只有到當進入一個執行上下文中,這個執行上下文的變量對象纔會被激活,所以才叫 activation object 吶,而只有被激活的變量對象,也就是活動對象上的各種屬性才能被訪問。
這裏附上一張貘大對於兩者關係的回答:
我們可以將變量對象的創建過程用代碼模擬一下:
1.我們用普通的對象來表示變量對象
var VO = {}; // 變量對象
2.而變量對象是執行上下文的一個屬性:
activeContext = {
VO: {
// 上下文數據(var, FD, function arguments)
}
};
3.當我們遇到下面的代碼時:
var a = 10;
function func(x){
var b = 20;
}
func(30);
4.對應的變量對象應該是:
// 全局變量對象
VO(Global) = {
a: 10,
func: reference to function plus(){}
}
// func函數上下文的變量對象
VO(func functionContext) = {
x: 30,
b: 20
};
因爲不同執行上下文下的變量對象稍有不同,所以我們分開來說。
二、全局變量對象
我們先了解一個概念,叫全局對象。在 W3School 中也有介紹:
全局對象是預定義的對象,作爲 JavaScript 的全局函數和全局屬性的佔位符。通過使用全局對象,可以訪問所有其他所有預定義的對象、函數和屬性。
1.可以通過 this 引用,在客戶端 JavaScript 中,全局對象就是 Window 對象。
console.log(this); //Window
2.全局對象是由 Object 構造函數實例化的一個對象。
console.log(this instanceof Object); // true
3.預定義了一堆,嗯,一大堆函數和屬性。
// 都能生效
console.log(Math.random()); //隨機數
console.log(this.Math.random()); //隨機數
4.作爲全局變量的宿主(很牛的樣子)
var a = 1;
console.log(this.a);// 1
5.客戶端 JavaScript 中,全局對象有 window 屬性指向自身。
var a = 1;
console.log(window.a); // 1
this.window.b = 2;
console.log(this.b); // 2
而全局上下文中的變量對象
就是全局對象!
三、函數上下文中的變量對象
在函數執行上下文中,VO是不能直接訪問的,此時由活動對象
(activation object,縮寫爲AO)扮演VO
的角色。
VO(functionContext) === AO
活動對象是在進入函數上下文時刻被創建的,它通過函數的arguments屬性初始化。arguments屬性的值是Arguments對象:
AO = {
arguments: <ArgO>
}
Arguments對象是活動對象的一個屬性,它包括如下屬性:
- callee — 指向當前函數的引用
- length — 真正傳遞的參數個數
- properties-indexes (字符串類型的整數) 屬性的值就是函數的參數值(按參數列表從左到右排列)。
- properties-indexes內部元素的個數等於arguments.length. properties-indexes 的值和實際傳遞進來的參數之間是共享的。
我們來看下面代碼:
function foo(x, y, z) {
// 聲明的函數參數數量arguments (x, y, z)
alert(foo.length); // 3
// 真正傳進來的參數個數(only x, y)
alert(arguments.length); // 2
// 參數的callee是函數自身
alert(arguments.callee === foo); // true
// 參數共享
alert(x === arguments[0]); // true
alert(x); // 10
arguments[0] = 20;
alert(x); // 20
x = 30;
alert(arguments[0]); // 30
// 不過,沒有傳進來的參數z,和參數的第3個索引值是不共享的
z = 40;
alert(arguments[2]); // undefined
arguments[2] = 50;
alert(z); // 40
}
foo(10, 20);
3.1 執行過程
執行上下文的代碼會分成兩個階段進行處理:分析和執行,我們也可以叫做:
- 進入執行上下文
- 代碼執行
3.2 進入執行上下文
當進入執行上下文時,這時候還沒有執行代碼,
變量對象會包括:
-
函數的所有形參 (如果是函數上下文)
- 由名稱和對應值組成的一個變量對象的屬性被創建
- 沒有實參,屬性值設爲 undefined
-
函數聲明
- 由名稱和對應值(函數對象(function-object))組成一個變量對象的屬性被創建
- 如果變量對象已經存在相同名稱的屬性,則完全替換這個屬性
-
變量聲明
- 由名稱和對應值(undefined)組成一個變量對象的屬性被創建;
- 如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性
舉個例子:
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
在進入執行上下文後,這時候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
3.3 代碼執行
在代碼執行階段,會順序執行代碼,根據代碼,修改變量對象的值
還是上面的例子,當代碼執行完後,這時候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
到這裏變量對象的創建過程就介紹完了,讓我們簡潔的總結我們上述所說:
- 全局上下文的變量對象初始化是全局對象;
- 函數上下文的變量對象初始化只包括 Arguments 對象;
- 在進入執行上下文時會給變量對象添加形參、函數聲明、變量聲明等初始的屬性值;
- 在代碼執行階段,會再次修改變量對象的屬性值;
思考題
最後讓我們看幾個例子:
1.第一題
function foo() {
console.log(a);
a = 1;
}
foo(); // ???
function bar() {
a = 1;
console.log(a);
}
bar(); // ???
第一段會報錯:Uncaught ReferenceError: a is not defined
。
第二段會打印:1
。
這是因爲函數中的 “a” 並沒有通過 var 關鍵字聲明,所有不會被存放在 AO 中。
第一段執行 console 的時候, AO 的值是:
AO = {
arguments: {
length: 0
}
}
沒有 a 的值,然後就會到全局去找,全局也沒有,所以會報錯。
當第二段執行 console 的時候,全局對象已經被賦予了 a 屬性,這時候就可以從全局找到 a 的值,所以會打印 1。
2.第二題
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
會打印函數,而不是 undefined 。
這是因爲在進入執行上下文時,首先會處理函數聲明,其次會處理變量聲明,如果如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。
參考
- 《你不知道的JavaScript》
- 深入理解變量對象
寫在最後
JavaScript內功基礎部分已經總結到第四篇了,本系列大約會有10篇文章,都是我們在面試最高頻的,但工作中常常被忽略的知識點。
JavaScript內功系列:
- this、call、apply詳解,系列(一)
- 從原型到原型鏈,系列(二)
- 從作用域到作用域鏈,系列(三)
- JavaScript中的執行上下文(四)
- 本文
- 下一篇預告,JavaScript中的立即執行函數
關於我
- 花名:餘光
- WX:j565017805
- 沉迷JS,水平有限,虛心學習中
其他沉澱
如果您看到了最後,不妨收藏、點贊、評論一下吧!!!
持續更新,您的三連就是我最大的動力,虛心接受大佬們的批評和指點,共勉!