Skip to content
本页目录

变量对象

在学习变量对象前,先让我们了解一下什么是执行上下文(Execution context,简称EC)?

执行上下文

我们知道JavaScript引擎在一段一段地分析和执行可执行代码(全局代码函数代码eval代码)时,遇到函数执行的时候,会做一些准备工作,这个准备工作就是创建一个执行上下文。 由于函数不会只是一个,故JavaScript引擎为了方便管理执行上下文,采用了栈结构,对其进行管理,即执行上下文栈(Execution Context Stack,简称ECS)。

每个执行上下文,都包含三个重要属性:

  1. 变量对象(Variable Object,通常又称VO):
  2. 作用域链(Scope Chain);
  3. this

变量对象

什么是变量对象呢?有何作用?

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。

变量对象通常可分为三类:

  1. 全局上下文的变量对象,像windowglobal
    • 全局VO是作用域链的头(终点)。
  2. 函数上下文的变量对象;
    • 在函数上下文中,活动对象(Activation Object, AO)来表示变量对象;
    • AO是在进入函数上下文时刻(即分析执行中的分析阶段)被创建的,它通过函数的arguments属性初始化。
  3. 块级(条件判断中的letconst)上下文的变量对象。

执行上下文的代码会被分成分析执行两个阶段进行处理。

分析阶段

分析阶段即进入执行上下文(此时还未执行代码),此阶段会初始化变量对象,其包括:

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

这里有个非常非常非常重要的概念:Hosting(就是函数声明提升变量声明提升

首先解释下这两概念是什么意思:

  • 函数提升: 通过function声明的函数,JavaScript引擎在解析代码时,将其提升至其对应作用域的最顶端,若该函数名与变量名相同,则变量名会被该函数覆盖(重复声明的相同函数名,以最靠后的声明为准,同赋值操作)。
  • 变量声明提升: 通过var关键字声明的变量,JavaScript引擎在解析代码时,会将该变量声明提升至其对应作用域的最顶端,若该变量名跟已经声明的形式参数名或函数名相同,则该变量名会被忽略。

特别注意:对于通过var关键字声明的变量,提升的只是声明赋值操作不会被提升!!!

来看个简单的例子:

JavaScript
kon()  // ?
var kon = function () {
  console.log('yui')
}
kon() // ?
function kon() {
  console.log('mio')
}
kon() // ?

高亮处的代码依次输出什么呢?mioyuiyui

为什么会如此呢?因为函数提升变量声明提升

上例中的代码完全等价于下例代码:

JavaScript
var kon // 被 行2 函数声明覆盖
function kon() {
  console.log('mio')
}
kon()
kon = function () { // 注意这里的赋值操作
  console.log('yui')
}
kon()
kon()

回到分析阶段,看个例子(假设第二个参数为可选参数):

JavaScript
function yui(name, age) {
  function sleep() { }
  let fav = '吉太'
  var sing = function () { }
  fav = 'azusa'
}
yui('yui') // 实参只传了一个

此阶段被初始化的活动对象AO

JavaScript
AO = {
  arguments: {
    0: 'yui',
    1: undefined,
    length: 2
  },
  name: 'yui',
  age: undefined,  
  sleep: ref to function sleep() { },
  sing: ref to FunctionExpression 'sing',  // 注意变量提升
  fav: undefined
}

执行阶段

在代码执行阶段,会顺序执行代码,并根据代码修改活动对象AO的值,此时的AO

JavaScript
AO = {
  arguments: {
    0: 'yui',
    1: undefined,
    length: 2
  },
  name: 'yui',
  age: undefined,  
  sleep: ref to function sleep() { },
  sing: ref to FunctionExpression 'sing',  // 注意变量提升
  fav: 'azusa'
}

简洁的总结我们上述:

  1. 全局上下文的变量对象初始化是全局对象;
  2. 函数上下文的变量对象初始化只包括Arguments对象;
  3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值;
  4. 在代码执行阶段,会再次修改变量对象的属性值。