[你不知道的javascript系列] - 今天聊一聊什么是作用域

674 阅读4分钟

讨论作用域,实际上就是讨论变量存储在程序的什么地方,程序要用变量的时候如何找到它们。 这真是一件有趣的事情!

一、JS的编译和传统的语言有何不同?

我们通常认为javascript是「动态的」、「解释性」的语言。但事实上,javascript是一门编译性语言。

传统的编译性语言,源代码执行之前,会经历三个步骤:

-w695

JS的引擎要做的事情,比这三个步骤复杂很多。

对于JS来说,编译的过程一般只在代码执行前的几微秒,甚至更短。

var a = 2 发生了什么?

在理解作用域前,我们介绍今天的三位主角

-w696

当JS遇到var a = 2的时候,会发生下面这些事情。

一、当js遇到var a的时候。编译器会先找作用域问一下,a这个变量是否存在?

-w745

二、编译器为引擎生成代码

生成的代码用来处理a = 2.

-w262

当引擎运行这段代码时,引擎会去找作用域,当前作用域有没有a变量?没有的话,上级作用域有吗?然后就会一直往上找。

如果找到a变量,就会对a变量进行赋值,如果没有找到,引擎就会抛出异常。

-w580

完整的流程大概是这样的:

-w757

LHS查询和RHS查询

  • 当变量出现在赋值操作的左侧的时候,进行LHS查询
  • 当变量出现在赋值操作的右侧的时候,进行RHS查询

其实这么说,并不准确。

更加容易理解的说法是这样的:

RHS 就是查找某个变量(或者是方法)

LHS 是视图查找变量的容器本身,从而可以对这个变量容器进行赋值。

这样说,还是有一点抽象。我们下面看一个例子,看看引擎和作用域是如何对话的。

function foo(a) {
    console.log(a)
} 
foo(2)

引擎:(第一次RHS查询)作用域老大,我要为foo进行RHS引用,你见过吗?
作用域:刚刚编译器声明了一个foo方法,给你。
引擎执行中...
引擎:(第一次LHS查询)作用域老大,我要为a进行LHS引用,你见过吗?
作用域:编译器把它声明成一个形参了,拿去吧
引擎执行中...把2赋值给a
引擎:(第二次RHS查询)作用域老大,我要为console进行RHS引用
作用域:它是一个内置对象,拿去
引擎执行中...查找到console下面还有一个log方法,继续执行
引擎:(第三次RHS查询)作用域老大,我要为a进行RHS引用,你帮我看看它还在不在?
作用域:放心,它没有发生变化,拿去用
引擎:谢谢,我继续执行....

由此可见,上面的代码分别进行了三次RHS查询一次LHS查询

作用域嵌套

-w583

引擎从当前作用域找,找不到会向父级作用域查找,如果依然没有找到,会一直找到全局作用域。

如果找到顶层,无论是否找到,都会停止查找

举个例子:

function foo (a) {
    console.log(a + b)
}
var b = 1;
foo(2)

引擎:(RHS查询)foo作用域,我要为b进行RHS引用,你见过吗?
foo作用域:啥玩意,没听说过,滚😡!
引擎:(继续查找)foo作用域的上级作用域兄弟,我要为b进行RHS引用,你见过吗?
全局作用域:我见过b,编译器刚刚声明了一个b,拿走吧

为啥要区分LHS和RHS查询?

划重点:LHS和RHS查询,查询不到的变量的时候,结果是不一样的

  • LHS查询,如果查询到全局作用域,都找不到变量,会很热心的声明一个变量,并返还给引擎 -w312

  • RHS查询,如果找不到变量,会抛出异常ReferenceError-w594

  • RHS查询,找到变量,但是进行不合理的应用的时候,会抛出异常TypeError

-w515

总结

1、作用域有一套自己的规则。用于确定在什么地方、如何查找变量。

2、如果查找的目的是赋值,进行LHS查询

3、如果查找的目的是获取变量的值,则进行RHS查询

4、LHS和RHS查询都会从当前作用域向上查找,如果找到则终止查询,如果找不到,一直找到顶层作用域。在顶层无论找到或者找不到,都终止查询。

5、不成功的RHS查询,会抛出ReferenceError错误。

6、成功的RHS查询,对变量进行不合理的操作,会抛出TypeError错误。

7、非严格模式下,LHS查询如果找不到变量,会自己生成一个变量。


都已经读到这里了,动动您贵手,点赞再走,祝你2021年,要啥都有。

我是阿飞,一个GTD践行者,深度工作践行者。


广告Time:

我做了一个公众号:青柠檬读书会,我希望成长的路上,有你相伴。