深入理解JavaScript 变量对象

目录
  • 前言
  • 变量对象
  • 全局上下文中变量对象
  • 函数上下文中的变量对象
    • 执行过程
    • 预编译
    • 代码执行
  • 总结
  • 练习题

前言

在上节《深入 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 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object,而只有被激活的变量对象,也就是活动对象,各种属性和方法才能被访问。

活动对象是在进入函数上下文时被创建的,它有函数的 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 变量对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript变量类型以及变量之间的转换你了解吗

    目录 1. 变量类型 1.1 变量类型的颜色 2. 字面量 3.数据类型转换 3.1. 转换为字符串型 1.加号拼接字符串 2.toString() 转成字符串 3.string(变量) 3.2转换为数字型(重点) 1.parseInt(string)函数-->整数数值型 2.parseFloat(string)函数-->浮点数数值型 3.利用number(变量) 4.利用了算数运算- + * / 隐式转换 3.3转换成布尔型 总结 1. 变量类型 <script> var num

  • JavaScript变量和变换详情

    目录 1.声明 2. 命名规范 3.变量声明的提升 4.数据类型的判断 5.数据类型的转换 6.将字符串转换为数字 7.变量的其他声明方式 8.数学对象 1.声明 使用变量之前务必通过关键字var进行声明.当一个变量仅是声明而未赋值时,变量初始值为undefined. var age console.log(age) 2. 命名规范 ①可以是数字.字母.下划线.$: ②不能以数字开头: ③不能是关键字.保留字. 3.变量声明的提升 age=10 console.log(age) var age

  • Javascript新手入门之字符串拼接与变量的应用

    1. 课程大纲 字符串拼接(+)的学习和应用 坐标变换在飞机大战游戏中的应用 2.1 字符串的拼接 在JS中使用"+"号,连接字符串.变量.数值等. 2.2 在警告框上显示朋友的数量 在警告框上显示朋友的数量,显示效果如下 声明变量 friends表示朋友的数量,在警告框上显示"我的朋友数量为:7",使用字符串拼接符"+" ,代码如下' var friends = 7; alert("我的朋友数量为:" +friends);

  • JavaScript中的变量声明你知道吗

    目录 变量 (一)var 1)关于var声明的变量的作用域 2)var声明提升(hoist) (二)let 1)与var不同,let声明的变量不会再作用域中被提升,这一现象被称为“暂时性死区” 2)全局声明 (三)Const 总结 变量 ECMAScript中,变量可以保存任何类型的数据(既可以是字符串也可以是数组也可以是别的……),也即“松散的”,变量只是一个用来区分的占位符,一共有var.const.let三个关键字用于声明变量(var在ECMAScrip所有版本可用,后两个只在ES6及以后

  • JavaScript中变量的用法

    一.JavaScript 变量 变量可以使用短名称(比如 x 和 y),也可以使用描述性更好的名称(比如 age, sum, totalvolume). 变量必须以字母开头 变量也能以 $ 和 _ 符号开头(不过我们不推荐这么做) 变量名称对大小写敏感(y 和 Y 是不同的变量) 注意:JavaScript 语句和 JavaScript 变量都对大小写敏感. 二.声明 JavaScript 变量 在 JavaScript 中创建变量通常称为"声明"变量. 我们使用 var 关键词来声明

  • JavaScript基础之变量

    目录 1.变量概述 1.1变量在内存中的存储 1.2 变量的使用 1.声明变量 2.赋值 3.变量的初始化 1.3变量语法扩展 1.更新变量 2.声明多个变量 3.声明变量特殊情况 1.5变量命名规范 总结 1.变量概述 1.1变量在内存中的存储 本质:变量是程序在内存中申请的一块用来存放数据的空间 1.2 变量的使用 变量的使用分为两步 :1.声明变量 2.赋值 1.声明变量 //声明变量 var age; //声明一个名称为age的变量 var是一个JS关键字,用来声明变量(variable

  • JavaScript把局部变量变成全局变量的方法

    首先我们要知道函数的自调用 函数的自调用--自调用函数 一次性的函数--声明的同时,直接调用了 例如: (function () { console.log("函数"); })(); 我们会看到浏览器直接打印 函数 两个字 页面加载后.这个自调用函数的代码就执行完了 使用形式 (function (形参) { })(实参); 注意 自调用构造函数的方式,分号一定要加上 那么如何把局部变量变成全局变量? 把局部变量给window就可以了 (function (win) { var num

  • 详解JS ES6变量的解构赋值

    1.什么是解构? ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构.它在语法上比ES5所提供的更加简洁.紧凑.清晰.它不仅能减少你的代码量,还能从根本上改变你的编码方式. 2.数组解构 以前,为变量赋值,我们只能直接指定值,比如 let a = 1; let b = 2; let c = 3; 现在可以用数组解构的方式来进行赋值 let [a, b, c] = [1, 2, 3]; console.log(a, b, c); // 1, 2, 3 这是数组解构最基本类型

  • JS变量提升原理与用法实例浅析

    本文实例讲述了JS变量提升.分享给大家供大家参考,具体如下: 该篇介绍什么是变量提升,写给像我一样的JS新手看的 简单来说变量提升就是 JS会把var变量的声明自动提升到作用域的顶部,即使你不想这样 一个例子: (局部变量与全局变量同名时 , 局部变量覆盖全局变量) var a="全局变量"; function test() { document.writeln(a); var a="局部变量"; document.writeln(a); } test(); 上例的

  • 简单谈谈JavaScript变量提升

    目录 前言 1. 什么变量提升? 2. 为什么会有变量提升? (1)提高性能 (2)容错性更好 3. 变量提升导致的问题 (1)变量被覆盖 (2)变量没有被销毁 4. 禁用变量提升 5. JS如何支持块级作用域 (1)创建执行上下文 (2)执行代码 6. 暂时性死区 总结 前言 在 ECMAScript6 中,新增了 let 和 const 关键字用来声明变量.在前端面试中也常被问到 let.const和 var 的区别,这就涉及到了变量提升.暂时性死区等知识点.下面就来看看什么是变量提升和暂时

随机推荐