前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >带你了解 JavaScript 作用域

带你了解 JavaScript 作用域

原创
作者头像
大发明家
发布2021-12-18 11:52:31
2720
发布2021-12-18 11:52:31
举报
文章被收录于专栏:技术博客文章技术博客文章

#什么是## 作用域的概念

现代编程语言的最基本功能之一就是能够存储变量当中的值,以便于之后的使用于修改。也正是这个功能将状态带给了程序。

在JavaScript中,作用域就是一套设计良好的规则来存储变量。

简述编译原理

通常我们会将JavaScript归类为“动态”或“解释执行“语言,但它实际上是一门编译语言。与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。

例如V8引擎,为了提高JavaScript代码的运行性能,在运行之前会先将其编译为本地的机器码,然后再去执行机器码,达到提升速度的目的。

分词/词法分析

这个过程将由字符组成的代码分解成对程序有意义的代码块,这些代码块被称为词法单元。

例如 var foo = 'bar' 通常会被分解为这些词法单元:var 、 foo 、 = 、 'bar'

解析/语法分析

这个过程将词法单元转换成一个“由元素逐级嵌套组成的代表程序语法的树“,这个树被称为“抽象语法树”(AST)。

image.png

代码生成

将上边的抽象语法树转换为机器可执行代码

JavaScript引擎比只有三个步骤的语言的编译器要复杂的多。例如在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。

对于JavaScript来说,大部分情况下编译发生在代码执行的前几微秒,任何代码片段在执行前都要进行编译。因此JavaScript编译器首先对 var

foo = 'bar' 进行编译,然后做好执行它的准备,并且通常马上就会执行它。

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

引擎:从头到尾负责整个JavaScript程序编译及执行过程undefined 编译器:负责语法分析及代码生成undefined 作用域:负责收集维护由所有变量组成的一系列查询

对于 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:变量出现在赋值操作的左侧(查找赋值操作的目标是谁)undefined RHS:变量在其他位置出现(查找值的源头)

代码语言:txt
复制
// 考虑下边的代码
代码语言:txt
复制
console.log(foo)

此例中 foo 的引用就是RHS查询,这里没有赋予 foo 任何值,相反的,我们需要查找 foo 的值,才能传递给log方法。

代码语言:txt
复制
// 相比之下
代码语言:txt
复制
foo = 'bar'

这里对 foo 的查询则是LHS查询,我们并不关心 foo 当前的值是什么, 只是想为这个赋值操作找到目标。

代码语言:txt
复制
// 再分析下边的代码
代码语言:txt
复制
function foo(a) {
代码语言:txt
复制
  console.log(a)
代码语言:txt
复制
}
代码语言:txt
复制
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查询

作用域的嵌套

我们在文章开始时说过,作用域是根据名称查找变量的一套规则。实际情况中需要同时顾及几个作用域。

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此在当前作用域中没有查找到目标变量时,会逐层向上查找直到全局作用域。

代码语言:txt
复制
// 考虑以下代码
代码语言:txt
复制
function foo(a) {
代码语言:txt
复制
  console.log(a + b)
代码语言:txt
复制
}
代码语言:txt
复制
var b = 258;
代码语言:txt
复制
foo(369)

对 b 进行的RHS查询无法在 foo 内部完成,但可以在上一级的作用域中完成(在此例中是全局作用域)。

LHS,RHS查询都会在作用域内逐层查找,直到找到为止(或到达全局作用域)。

ReferenceError

上一节提到了LHS,RHS都会在作用域内逐层查找变量,但如果到达全局作用域仍然没有找到变量怎么办呢?

这时区分LHS和RHS查询的意义就体现出来了。

如果RHS查询在所有嵌套的作用域中都没有找到所需变量,引擎就会抛出 ReferenceError。

如果LHS查询在所有嵌套的作用域中都没有找到所需变量,引擎就会在全局作用域中创建一个具有该名称的变量,并将其返回给引擎。

注意:ES5中引入了严格模式,与普通模式相比,严格模式其中一个不同就是进制自动或隐式的创建全局变量。因此在严格模式下LHS查询失败时不会创建并返回全局变量,引擎同样会抛出

ReferenceError。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 简述编译原理
    • 分词/词法分析
      • 解析/语法分析
        • 代码生成
        • 引擎、编译器、作用域在赋值操作中的配合
        • 作用域的嵌套
        • ReferenceError
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档