四、JavaScript之变量对象
在前面的文章《JavaScript之执行上下文栈》中讲到,当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
执行上下文的组成代码示例:
1 | const ExecutionContextObj = { |
本文主要记录创建变量对象的过程。
变量对象
变量对象是与执行上下文相关的数据作用域,存储了上下文中定义的变量和函数声明。
变量对象式一个抽象的概念,在不同的上下文中,表示不同的对象:
全局上下文
全局执行上下文的变量对象
- 全局执行上下文中,变量对象就是全局对象。
- 在顶层js代码中,this指向全局对象,全局变量会作为该对象的属性来被查询。在浏览器中,window就是全局对象。
这样我们就能理解为什么,在全局上下文中声明一个变量时,我们能够通过全局对象的一个属性来间接引用它(例如,当变量的名称事先不知道时):
1 | var a = 'test'; |
函数上下文
函数执行上下文的变量对象
- 函数上下文中,变量对象VO就是活动对象AO。
- 初始化时,带有arguments属性。
Arguments 对象(简称 ArgO)是一个位于函数上下文的激活对象中的对象,包含以下属性:
callee - 链接到正在执行的函数;
length -实际传递的参数数量;
properties-indexes(数字,缩减为字符串),其中的值是函数的形参(在参数列表中从左到右)。这些索引属性的数量 == arguments.length。arguments 对象的 index 属性值和存在的形参是可以互换的:
1 | function foo(x, y, z) { |
执行过程
执行上下文的代码会分成两个阶段进行处理:分析(进入执行上下文)和执行(代码执行):
进入执行上下文
当进入执行上下文时,这时候还没有执行代码,
变量对象会包括:
- 函数的所有形参 (如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为 undefined
- 函数声明
- 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
- 如果变量对象已经存在相同名称的属性,则完全替换这个属性
- 变量声明
- 由名称和对应值(undefined)组成一个变量对象的属性被创建;
- 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
举个例子:
1 | function foo(a, b) { |
在进入执行上下文后,这时候的 AO 是:
1 | AO = { |
请注意,AO 中不包含“x”函数。这是因为,“x”是不是一个函数声明,而是一个FunctionExpression(函数表达式)是不影响VO。然而,函数“_e”也是一个表达式函数,但是,正如我们将在下面看到的,通过将对其的引用分配给变量“e”,它可以通过“e”使用。您其他帖子中阅读 FunctionDeclaration 和 FunctionExpression 之间的区别。
代码执行
在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
还是上面的例子,当代码执行完后,这时候的 AO 是:
1 | AO = { |
再次注意 FunctionExpression_e仅以变量为代价保留在内存中e。FunctionExpressionx没有进入 AO/VO:也就是说,如果你试图在代码中x声明之前或之后调用一个函数,就会出现“x未定义”错误。未保存的 FunctionExpression 只能与声明一起调用,或递归调用。
来看一个示例:
1 | console.log(x); // function |
为什么第一个输出中的“x”是一个函数,甚至在申明之前可用?为什么不是 10 或 20?因为,根据规则——VO在进入上下文时填充了函数声明,在同一个地方,进入时声明了变量“x”,但VO中的变量的优先级低于函数声明,因此,在进入时,VO填充会以如下方式发生:
1 | VO = {}; |
但是已经在执行代码时,VO被修改成这样:
1 | VO['x'] = 10; |
再看一个示例:
1 | if (true) { |
在这个例子中,我们看到变量在进入上下文时就会进入 VO(例如,永远不会执行 else 块,但是,变量“b”仍然存在于 VO 中)
关于变量
在JavaScript中,使用var申明和不适用var申明是有区别,请记住:变量仅使用 var 关键字声明。
比如:
1 | console.log(a); // undefined |
这里我们看到”b is not defined”,因为 这不是一个变量,“b”只会在代码执行时创建;
关于变量的另一个重点,与简单属性不同,变量接收{DontDelete}属性,这意味着无法使用delete运算符删除:
1 | a = 10; |
到这里变量对象的创建过程就介绍完了,让我们简洁的总结我们上述所说:
全局上下文的变量对象初始化是全局对象
函数上下文的变量对象初始化只包括 Arguments 对象
在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
在代码执行阶段,会再次修改变量对象的属性值
在进入执行上下文时,var声明的变量才会被添加并赋初始值
参考文献
http://dmitrysoshnikov.com/ecmascript/ru-chapter-2-variable-object/