深入理解 JavaScript 变量对象

作者:函数茶馆时间:2026-10-25 10:43:02浏览:583

本文深入解析 JavaScript 变量对象(VO)与活动对象(AO)的机制。详细阐述全局上下文中全局对象的特性,以及函数上下文中 AO 在预编译阶段对形参、变量声明和函数声明的处理流程,并通过代码示例说明执行阶段对 AO 值的修改过程。

前言

在上一节《深入 JavaScript 执行上下文栈——Web 前端进阶系列第三节》中,我们探讨了 JavaScript 引擎在执行可执行代码时会创建相应的执行上下文。

每个执行上下文都包含三个关键属性:

  • 变量对象 (Variable object, VO)
  • 作用域链 (Scope chain)
  • this

本节将重点讲解变量对象。

变量对象

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

执行上下文主要分为两种:全局上下文和函数上下文。接下来我们将分别介绍这两种上下文中的变量对象。

全局上下文中变量对象

在全局上下文中,变量对象即为全局对象。

关于全局对象,W3school 有以下介绍:

  • 全局对象是预定义的对象,作为 JavaScript 全局函数和全局属性的占位符。通过全局对象,可以访问所有其他预定义的对象、函数和属性。

  • 在顶层 JavaScript 代码中,可以使用关键字 this 引用全局对象。全局对象位于作用域链的最底层,这意味着所有非限定性的变量和函数名都会作为该对象的属性进行查询。

  • 由于全局对象位于作用域链的最底层,在顶层 JavaScript 代码中声明的变量都会成为全局对象的属性。

上述概念可能较为抽象,接下来我们将结合具体示例进行进一步讲解。

  • 在顶层 JavaScript 代码中,可以使用关键字 this 引用全局对象。在浏览器 JavaScript 环境中,全局对象是 window;在 Node.js 环境中,全局对象是 global。
console.log(this); // window
console.log(this === window); // true
  • 全局对象作为 JavaScript 全局函数和全局属性的占位符。在顶层 JavaScript 代码中声明的变量都会成为全局对象的属性。
// 声明的变量成为了全局对象的属性
var a = 1;
console.log(this.a); // 1

// 声明的函数成为了全局对象的属性
function b() {}
console.log(this.b); // function b
  • 通过全局对象,可以访问全局函数和全局属性,同时也能访问所有其他预定义的对象、函数和属性。
// 使用全局对象访问全局属性 Math,它是一个对象,它拥有 random 方法。
console.log(this.Math.random()); // 打印一个随机数
  • 所有非限定性的变量和函数名都会作为该对象的属性进行查询。
// 这里的 Math 是非限定性的函数名
console.log(Math.random()); // 打印一个随机数
  • 全局对象是 Object 构造函数的实例,这意味着 Object.prototype(原型)上预定义的属性和方法可以通过全局对象访问。
console.log(this instanceof Object); // true
  • 在浏览器 JavaScript 中,全局对象具有 window 属性且指向自身。
console.log(this.window === this); // true

函数上下文中的变量对象

在函数上下文中,我们使用活动对象 (activation object, AO) 来表示变量对象。

活动对象和变量对象本质上是同一个概念,区别在于变量对象是规范或引擎实现层面的抽象,无法在 JavaScript 环境中直接访问。只有当进入一个执行上下文时,该上下文的变量对象才会被激活,因此称为活动对象。只有被激活的变量对象(即活动对象),其各种属性和方法才能被访问。

活动对象在进入函数上下文时创建,并以函数的 arguments 属性作为初始化属性。arguments 属性的值是 Arguments 对象。

执行过程

函数上下文的代码执行过程分为两个阶段:预编译和执行。

预编译

  • 创建 AO 对象,查找形参和变量声明

  • 将形参和变量名作为 AO 对象的属性名,值设为 undefined

  • 将实参赋值给形参,使实参与形参统一

  • 查找函数声明,值为函数体

我们来看一个例子:

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;
}

foo(1);

该函数在预编译完成后,AO 将变为:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

代码执行

在代码执行阶段,代码会按顺序执行。根据代码逻辑,修改变量对象的值。

上述例子在代码执行完毕后,AO 将变为:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

总结

至此,变量对象的创建过程介绍完毕,我们进行总结:

  • 全局上下文的变量对象初始化为全局对象
  • 函数上下文的变量对象初始化仅包括 Arguments 对象
  • 在进入执行上下文时,会给变量对象添加形参、变量声明、函数声明等初始属性值(预编译)
  • 在代码执行阶段,会修改变量对象的属性值

练习题

  • 第一题

观察以下两段代码,分别会打印什么?

function foo() {
  console.log(a);
  a = 1;
}

foo();
function bar() {
  a = 1;
  console.log(a);
}
bar();

第一段代码会报错:Uncaught ReferenceError: a is not defined。

第二段代码会打印:1。

因为第一段代码中 a 没有变量声明,所以函数执行上下文的 AO 中没有 a 变量的定义,此时 AO 的值为:

AO = {
    arguments: {
        length: 0
    }
}

执行打印时,在函数执行上下文的 AO 中未找到 a 变量的定义,随后去全局上下文查找,发现全局也没有,因此报未定义错误。

第二段代码中,未使用 var 关键字声明的变量会成为全局对象的属性,因此执行打印时,会从全局对象找到 a 的值,从而打印 1。

  • 第二题
console.log(foo);

function foo() {}

var foo = 1;

会打印 foo 函数,而不是 undefined。

因为在预编译的第 4 步,会查找函数声明,值为函数体,即函数声明会覆盖变量声明。

到此这篇关于深入理解 JavaScript 变量对象的文章就介绍到这了,更多相关 JavaScript 变量对象内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

作者最新文章