Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jtwang7 opened this issue May 9, 2021 · 0 comments
Open

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

jtwang7 opened this issue May 9, 2021 · 0 comments

Comments

@jtwang7
Copy link
Owner

jtwang7 commented May 9, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant