什么是JavaScript的作用域

作用域的概念
现代编程语言的最基本功能之一就是能够存储变量当中的值,以便于之后的使用于修改。也正是这个功能将状态带给了程序。
在JavaScript中,作用域就是一套设计良好的规则来存储变量。

简述编译原理

通常我们会将JavaScript归类为“动态”或“解释执行“语言,但它实际上是一门编译语言。与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。
例如V8引擎,为了提高JavaScript代码的运行性能,在运行之前会先将其编译为本地的机器码,然后再去执行机器码,达到提升速度的目的。
  • 分词/词法分析
    这个过程将由字符组成的代码分解成对程序有意义的代码块,这些代码块被称为词法单元
    例如 var foo = 'bar' 通常会被分解为这些词法单元:var 、 foo 、 = 、 'bar'
  • 解析/语法分析
    这个过程将词法单元转换成一个“由元素逐级嵌套组成的代表程序语法的树“,这个树被称为“抽象语法树”(AST)。
    image
  • 代码生成
    将上边的抽象语法树转换为机器可执行代码
JavaScript引擎比只有三个步骤的语言的编译器要复杂的多。例如在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。
对于JavaScript来说,大部分情况下编译发生在代码执行的前几微秒,任何代码片段在执行前都要进行编译。因此JavaScript编译器首先对 var foo = 'bar' 进行编译,然后做好执行它的准备,并且通常马上就会执行它。

引擎、编译器、作用域在赋值操作中的配合

  • 引擎:从头到尾负责整个JavaScript程序编译及执行过程
  • 编译器:负责语法分析及代码生成
  • 作用域:负责收集维护由所有变量组成的一系列查询
对于 var foo = 'bar' 这段代码,大家很有可能认为是一句简单的声明。而事实上JavaScript执行时会将它分成两个完全不同的声明。
  1. 编译器首先将这段代码分解成词法单元,然后解析为树结构。(在下一步代码生成时,处理这段代码的方式会跟预期有所不同)
  2. 遇到 var foo ,编译器会检查作用域是否已有同名变量存在。如果有的话编译器会忽略声明,继续编译。否则它会生成代码在当前作用域的变量集合中声明一个新的变量,命名为 foo
  3. 接下来编译器会为引擎生成运行时所需代码,用来处理 foo = 'bar' 这个赋值操作。
  4. 引擎运行时会首先查询当前作用域是否存在叫做 foo 的变量。如果有引擎则会使用这个变量,否则会一直向上层作用域查找。
  5. 如果最终找到了 foo 这个变量,就会将 'bar' 赋给它,否则抛出异常。
总结:变量的赋值会执行两个动作:首先是编译器在当前作用域中声明变量(如果变量未被声明过);接着运行时引擎在作用域查找该变量,能找到就会对它赋值。

LHS查询 vs RHS查询

引擎执行编译器生成的代码时,会通过查找 foo 来判断是否已经声明过。查找的过程由作用域来协助。在我们的例子中,引擎为变量 foo 进行的时LHS查询,还有另一个查找类型叫RHS查询。顾名思义,它们的意思是Left hand side 和 Right hand side
  • LHS:变量出现在赋值操作的左侧(查找赋值操作的目标是谁)
  • RHS:变量在其他位置出现(查找值的源头)
// 考虑下边的代码
console.log(foo)
此例中 foo 的引用就是RHS查询,这里没有赋予 foo 任何值,相反的,我们需要查找foo 的值,才能传递给log方法。
// 相比之下
foo = 'bar'
这里对 foo 的查询则是LHS查询,我们并不关心 foo 当前的值是什么, 只是想为这个赋值操作找到目标。
// 再分析下边的代码
function foo(a) {
  console.log(a)
}

foo('bar')

这段代码里既有LHS查询又有RHS查询
  1. 最后一行 foo(...) 函数的调用需要对 foo 进行RHS查询 → 找到 foo 的值
  2. 入参时存在隐式的 a = 'bar' ,需要对 a 进行LHS查询
  3. console.log(a) 对 a 进行RHS查询
  4. console.log(...) 本身也需要对 console 对象进行RHS查询

作用域的嵌套

我们在文章开始时说过,作用域是根据名称查找变量的一套规则。实际情况中需要同时顾及几个作用域。
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此在当前作用域中没有查找到目标变量时,会逐层向上查找直到全局作用域。
// 考虑以下代码
function foo(a) {
  console.log(a + b)
}

var b = 258;

foo(369)

对 b 进行的RHS查询无法在 foo 内部完成,但可以在上一级的作用域中完成(在此例中是全局作用域)。
LHS,RHS查询都会在作用域内逐层查找,直到找到为止(或到达全局作用域)。

ReferenceError

上一节提到了LHS,RHS都会在作用域内逐层查找变量,但如果到达全局作用域仍然没有找到变量怎么办呢?
这时区分LHS和RHS查询的意义就体现出来了。
如果RHS查询在所有嵌套的作用域中都没有找到所需变量,引擎就会抛出 ReferenceError
如果LHS查询在所有嵌套的作用域中都没有找到所需变量,引擎就会在全局作用域中创建一个具有该名称的变量,并将其返回给引擎。
注意:ES5中引入了严格模式,与普通模式相比,严格模式其中一个不同就是进制自动或隐式的创建全局变量。因此在严格模式下LHS查询失败时不会创建并返回全局变量,引擎同样会抛出 ReferenceError

总结

  • 作用域是一套规则,用于确定在何处以及如何查找变量。如果查找的目的是对变量赋值,会使用LHS查询;如果目的是获取变量的值,会使用RHS查询。
  • JavaScript引擎会在代码执行前对其进行编译。在这个过程中,像 var foo = 'bar' 这种声明会被分解成两个独立的步骤。
    1. var foo 在其作用域中声明新的变量。此操作在代码执行前进行。
    2. 接下来 foo = 'bar' 会查询(LHS)变量 foo 并对其赋值。
  • LHS和RHS查询都会在当前执行作用域中开始,如果有需要(没有在当前作用域找到变量)就会向上级作用域继续查找目标变量,一直抵达全局作用域,无论找到与否都会停止。
  • 不成功的RHS查找会导致抛出 ReferenceError ,不成功的LHS查找会导致自动隐式地创建一个全局变量(非严格模式下),或者抛出 ReferenceError(严格模式下)。
(0)

相关推荐

  • JavaScript作用域

    作用域下的变量内存 · 全局变量(包括函数内无声名变量),在浏览器关闭时销毁 · 局部变量(包括函数形参),在执行完函数程序后销毁 作用域链 就近翻上查找变量输出 块级作用域 由于JavaScript ...

  • 作用域的理解-1

    程序var a = 2 处理时发生了啥 了解将要参与到对程序 var a = 2; 进行处理的过程中的演员们. 引擎 从头到尾负责整个 JavaScript 程序的编译及执行过程. 编译器 引擎的好朋 ...

  • JavaScript 之 作用域

    学习目标:能够说出Javascript的两种作用域 能够区分全局变量和局部变量 能够说出如何在作用域链中查找变量的值 1.作用域 <script> //1.javaScript作用域:就是 ...

  • 深入理解JavaScript作用域和作用域链

    目录 前言 作用域(Scope) 1.什么是作用域 2.全局作用域和函数作用域 3.块级作用域 作用域链 1.什么是自由变量 2.什么是作用域链 3.关于自由变量的取值 作用域与执行上下文 解释阶段: ...

  • javascript执行上下文、作用域与闭包(第六篇)

    终于讲到闭包了,当你在百度上搜索闭包时,你会被搜索出来的结果吓一跳,我的天,为什么说得都不一样?直到把所有的解释都看过了,我就只想说一句,到底谁说的是对的- 在这么多的不同解释里,我认真思考了很久,到 ...

  • C++的标识符的作用域与可见性

    下面是关于C++的标识符的作用域与可见性学习记录,仅供参考 标识符的作用域与可见性 作用域是一个标识符在程序正文中有效的区域. 作用域分类 ①函数原型作用域 ②局部作用域(快作用域) ③类作用域 ④文 ...

  • 报告:JavaScript 开发者达1380 万,C#超越 PHP,Rust 增长最快

    出品|开源中国 文|白开水 研究公司 SlashData 最新发布的"State of the Developer Nation"第 20 版报告指出,全球开发者社区在过去六个月中 ...

  • javascript中的闭包这一篇就够了

    前端技术优选 今天 以下文章来源于程序员成长指北 ,作者koala 程序员成长指北专注 Node.js 技术栈分享,从 前端 到 Node.js 再到 后端数据库,祝您成为优秀的高级 Node.js ...

  • JavaScript 事件循环:从起源到浏览器再到 Node.js

    冰森 前端技术优选 今天 很多文章都在讨论事件循环 (Event Loop) 是什么,而几乎没有人讨论为什么 JavaScript 中会有事件循环.博主认为这是为什么很多人都不能很好理解事件循环的一个 ...

  • 用JavaScript打造AI应用-从Nodejs SDK 看DuerOS的技能开发

    为什么要掌握JavaScript呢? 使用JavaScript能能否开发AI应用么? 答案是肯定的. 全栈语言JavaScript 就全栈编程语言而言,与python 并驾齐驱的要算是JavaScri ...