灵魂三问
- 什么是作用域
- 为什么要有作用域
- 作用域起到了什么作用
带着这些问题,开始以下简单的探讨
什么是作用域
简单来说。作用域就是储存和查找变量的一套完整的规则。
为什么要有作用域
状态
每种编程语言都可以记录变量等数据状态,然后在程序需要的位置去引用这些数据,而作用域就是储存这些数据的地方,它将这种状态提供给了程序,使程序可以进行更复杂的操作。而不是只能进行简单的无状态的操作。
作用域时做什么的
要回答这个问题,可能需要一些其他知识。
理解作用域
首先来看一下 var a = 1 这段程序的变异和执行的过程。
-
引擎:从头到尾负责整个 js 的编译和执行过程(还包括一些运行前的优化)
-
编译器:负责将用户代码,转换为可执行的机器码。 编译器的大致执行过程如下:
1. 词法分析阶段 这个阶段会将 `var a = 2` 分解为 var,a,=,2 这几个词。 2. 语法分析阶段 这个阶段会将上述的词法流(数组)转换为一个树状结构的对象,也就是抽象语法是(AST) 3. 代码生成阶段 会将 2 中的语法树转换为可执行代码这里注意,js 编译器不止仅仅做了这些事情,还会在代码执行前对代码进行一下优化等操作。
-
作用域:负责收集并记录编译过程中声明的标识符(变量)组成的一套查询,并有一套严密的查询规则,确定当前执行的代码可以对这些标识符的访问权限。
通过这个过程可以看到,作用域相当于程序的状态管理器,它统一管理这程序的各种状态。(这里想起了redux)
怎么查找
理解了作用域的作用,那 b = a 这段代码的查找规则是什么呢?
- LHS:左查找,表示在赋值操作的左侧,也就是赋值操作的目标是谁
- RHS:右查找,表示在赋值操作的右侧,也就是赋值操作的源头是谁
上述代码中,a 会进行右查询,就是查询 a 的值;b 会进行左查询,就是给 b 赋什么值。
关于作用域嵌套
当一个快或者函数嵌套在另一个快或者函数中时,就会发生作用域嵌套。这里有一个经典的作用域链查找。
作用域链查找:其实就是从当前作用域出发,逐层的向上一级作用域查找,直到找到为止,或者找不到。
关于找不到报错的问题
- 执行 LHS 如果没有找到,就会在全局作用域中声明这个变量。
- 执行 RHS 如果没找到值,就会抛出 referenceError 的错误,表示在当前嵌套作用域的所有层级都找不到这个变量
关于作用域需要注意的点
一般作用的问题分为两种情况:
- 当前作用域中声明了和外层作用域相同的变量
var i = 10;
function a() {
i = 20;
console.log(i); // 20
for(var i=0;i<6;i++) {
console.log(i); // 0-5
}
console.log(this.i);
console.log(i); // 6
}
a();
console.log(i);
这种情况下,每部作用域的变量不会去覆盖全局变量,具体参考作用域查询规则
- 当前作用域没有声明同名变量
var i = 10;
function a() {
i = 20;
console.log(i); // 20
console.log(this.i);
console.log(i); // 20
}
a();
console.log(i);
这种情况下,内部作用域的变量引用的就是外部作用域中的变量,这时重新赋值会发生值得覆盖。