Skip to content

JS深入浅出 - 变量对象 #4

Open
@jtwang7

Description

@jtwang7

JS深入浅出 - 变量对象

参考文章:
JavaScript深入之变量对象
理解JavaScript的执行上下文
你不知道的JavaScript】(三)执行上下文及其生命周期

1. 变量对象(Variable Object,VO)

概念

在当前执行上下文中,用于存储所有定义变量和函数声明的对象。

内容

变量对象在执行上下文的不同阶段,包含的内容有所不同:

  1. 在执行上下文创建阶段,变量对象包括:
  • arguments 类数组对象 (在函数上下文中)

    arguments 对象::JS 调用函数时,会为其创建一个 Arguments 对象,并自动初始化局部变量 arguments,指代该 Arguments对象,所有作为参数传入的值都会成为 Arguments 对象的数组元素。换句话说,Arguments 对象本质上打包了该函数的所有参数,并对外抛出了一个 arguments 接口(类数组),在函数中可以访问该接口获取参数数据。

  • 函数的所有形参 (在函数上下文中)
    • 由名称和对应值组成的一个变量对象的属性被创建
    • 没有实参,属性值设为 undefined
  • 函数声明
    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性
  • 变量声明
    • 由名称和对应值 undefined (或者 <uninitialized>)组成一个变量对象的属性被创建;
    • 如果变量名称跟已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性

    注:上下文创建阶段,变量对象的变量声明 var 对应的值为 undefined,ES6 下通过 letconst 声明的变量不会被初始化,结果表示为 <uninitialized>
    换个角度理解:在上下文创建阶段收集该上下文内所有定义的变量和函数声明,存储到变量对象内,本质上实现了变量提升和函数提升。var 声明的变量被扫描到后会先被初始化为 undefined,之后在执行阶段被赋予新值。而let 或者 const 声明的变量由于 ES6 暂时性死区的特性,尽管会被变量提升,但不会被初始化,其赋值过程只会发生在执行阶段的赋值语句上。

  1. 在执行上下文执行阶段,会顺序执行代码并更新变量对象的值。

变量对象创建过程

  1. 若在函数执行上下文中,建立arguments对象。反之跳过该步骤。
  2. 若在函数执行上下文中,扫描当前上下文的函数形参,以形参名和对应传参值创建为变量对象的属性,若形参对应位置没有传参,则赋值 undefined
  3. 扫描当前上下文的函数声明(使用function关键字声明的函数,函数表达式被归类为变量声明)。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
  4. 扫描当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为 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 区别

活动对象和变量对象本质上是相同的。存在几点细微的差别:

  1. 激活状态
  • 变量对象是规范上的或者说是引擎实现上的,其不可在 JavaScript 环境中访问
  • 活动对象是被激活的变量对象,其属性可以被访问。
  1. 创建时刻
  • 变量对象在执行上下文被创建时生成。
  • 活动对象在函数执行上下文执行时由变量对象激活生成。

总结

  • 活动对象 AO 实际是变量对象 VO 在函数执行上下文中的特殊表示,当函数被调用时,其执行上下文在创建阶段会生成相应的变量对象,而未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。
  • 我们可以认为 AO 和 VO 是同一对象,只是处于函数执行上下文的不同生命周期(AO 在执行阶段,VO 在创建阶段)。

注:VO -> AO 的转变发生在执行上下文创建阶段完成后,执行阶段内最开始,即是在执行阶段内发生的,但此时会先将变量对象激活为活动对象,再进行执行阶段的后续操作(变量赋值,函数引用等)。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions