Description
JS深入浅出 - 变量对象
参考文章:
★ JavaScript深入之变量对象
理解JavaScript的执行上下文
你不知道的JavaScript】(三)执行上下文及其生命周期
1. 变量对象(Variable Object,VO)
概念
在当前执行上下文中,用于存储所有定义变量和函数声明的对象。
内容
变量对象在执行上下文的不同阶段,包含的内容有所不同:
- 在执行上下文创建阶段,变量对象包括:
- arguments 类数组对象 (在函数上下文中)
arguments 对象::JS 调用函数时,会为其创建一个 Arguments 对象,并自动初始化局部变量 arguments,指代该 Arguments对象,所有作为参数传入的值都会成为 Arguments 对象的数组元素。换句话说,Arguments 对象本质上打包了该函数的所有参数,并对外抛出了一个 arguments 接口(类数组),在函数中可以访问该接口获取参数数据。
- 函数的所有形参 (在函数上下文中)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为
undefined
- 函数声明
- 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
- 如果变量对象已经存在相同名称的属性,则完全替换这个属性
- 变量声明
- 由名称和对应值
undefined
(或者<uninitialized>
)组成一个变量对象的属性被创建; - 如果变量名称跟已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性
注:上下文创建阶段,变量对象的变量声明
var
对应的值为undefined
,ES6 下通过let
和const
声明的变量不会被初始化,结果表示为<uninitialized>
。
换个角度理解:在上下文创建阶段收集该上下文内所有定义的变量和函数声明,存储到变量对象内,本质上实现了变量提升和函数提升。var 声明的变量被扫描到后会先被初始化为 undefined,之后在执行阶段被赋予新值。而let 或者 const 声明的变量由于 ES6 暂时性死区的特性,尽管会被变量提升,但不会被初始化,其赋值过程只会发生在执行阶段的赋值语句上。 - 由名称和对应值
- 在执行上下文执行阶段,会顺序执行代码并更新变量对象的值。
变量对象创建过程
- 若在函数执行上下文中,建立arguments对象。反之跳过该步骤。
- 若在函数执行上下文中,扫描当前上下文的函数形参,以形参名和对应传参值创建为变量对象的属性,若形参对应位置没有传参,则赋值
undefined
。 - 扫描当前上下文的函数声明(使用function关键字声明的函数,函数表达式被归类为变量声明)。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
- 扫描当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为
undefined
或<uninitialized>
。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined
,则会直接跳过,原属性值不会被修改。
举例
// Example
function foo(a) {
var b = 2;
function c() {}
const d = function() {};
b = 3;
}
foo(1);
上下文创建阶段
VO = {
// arguments 对象
arguments: {
0: 1,
length: 1
},
// 形参
a: 1,
// 变量声明 (var)
b: undefined,
// 函数声明
c: reference to function c(){},
// 变量声明 (let or const)
d: <uninitialized>
}
上下文执行阶段
// VO 激活为 AO,其内部属性可被访问。
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
// 变量值被赋值语句更新
b: 3,
c: reference to function c(){},
// 变量值被赋值语句更新
d: reference to FunctionExpression "d"
}
分类
根据执行上下文的不同,变量对象也分为:
- 全局执行上下文中的变量对象
全局上下文中变量对象实际就是全局对象。
- 函数执行上下文中的变量对象
函数上下文中变量对象在执行阶段由活动对象(Activation Object,AO)代替。
在JS深入浅出 - 执行上下文 (Excution Context)中提及,变量对象是执行上下文的一个属性,在执行上下文创建阶段生成。
2. 全局对象
在全局执行上下文中的变量对象,实际上就是全局对象。
在浏览器中,全局对象为 window 对象,在 Nodejs 中,全局对象为 global 对象。
在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象(非严格模式)。
- 概念:存储 JavaScript 全局函数和全局属性的预定义对象。
- 在作用域链中,全局对象是作用域链的头。
在作用域链中提及,在函数执行上下文的创建阶段,其变量对象会被添加至函数内部属性
[[scope]]
(函数在定义时,会创建一个数组,并将父级以上的所有变量对象保存至数组内,作为其内部属性)的头部,构成作用域链。在全局环境中,函数定义时[[scope]]
属性内保存的就是全局环境的变量对象(全局对象)。因此构建的作用域链头节点指向的就是全局对象。
3. 活动对象(Activation Object,AO)
在函数执行上下文中,用活动对象来表示执行环境中的变量对象。
VO/AO 区别
活动对象和变量对象本质上是相同的。存在几点细微的差别:
- 激活状态
- 变量对象是规范上的或者说是引擎实现上的,其不可在 JavaScript 环境中访问。
- 活动对象是被激活的变量对象,其属性可以被访问。
- 创建时刻
- 变量对象在执行上下文被创建时生成。
- 活动对象在函数执行上下文执行时由变量对象激活生成。
总结
- 活动对象 AO 实际是变量对象 VO 在函数执行上下文中的特殊表示,当函数被调用时,其执行上下文在创建阶段会生成相应的变量对象,而未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。
- 我们可以认为 AO 和 VO 是同一对象,只是处于函数执行上下文的不同生命周期(AO 在执行阶段,VO 在创建阶段)。
注:VO -> AO 的转变发生在执行上下文创建阶段完成后,执行阶段内最开始,即是在执行阶段内发生的,但此时会先将变量对象激活为活动对象,再进行执行阶段的后续操作(变量赋值,函数引用等)。