关于作用域(简单描述)

165 阅读3分钟

灵魂三问

  1. 什么是作用域
  2. 为什么要有作用域
  3. 作用域起到了什么作用

带着这些问题,开始以下简单的探讨

什么是作用域

简单来说。作用域就是储存和查找变量的一套完整的规则。

为什么要有作用域

状态

每种编程语言都可以记录变量等数据状态,然后在程序需要的位置去引用这些数据,而作用域就是储存这些数据的地方,它将这种状态提供给了程序,使程序可以进行更复杂的操作。而不是只能进行简单的无状态的操作。

作用域时做什么的

要回答这个问题,可能需要一些其他知识。

理解作用域

首先来看一下 var a = 1 这段程序的变异和执行的过程。

  1. 引擎:从头到尾负责整个 js 的编译和执行过程(还包括一些运行前的优化)

  2. 编译器:负责将用户代码,转换为可执行的机器码。 编译器的大致执行过程如下:

     1. 词法分析阶段
         这个阶段会将 `var a = 2` 分解为 var,a,=,2 这几个词。
     2. 语法分析阶段
         这个阶段会将上述的词法流(数组)转换为一个树状结构的对象,也就是抽象语法是(AST)
     3. 代码生成阶段
         会将 2 中的语法树转换为可执行代码
    

    这里注意,js 编译器不止仅仅做了这些事情,还会在代码执行前对代码进行一下优化等操作。

  3. 作用域:负责收集并记录编译过程中声明的标识符(变量)组成的一套查询,并有一套严密的查询规则,确定当前执行的代码可以对这些标识符的访问权限。

通过这个过程可以看到,作用域相当于程序的状态管理器,它统一管理这程序的各种状态。(这里想起了redux

怎么查找

理解了作用域的作用,那 b = a 这段代码的查找规则是什么呢?

  1. LHS:左查找,表示在赋值操作的左侧,也就是赋值操作的目标是谁
  2. RHS:右查找,表示在赋值操作的右侧,也就是赋值操作的源头是谁

上述代码中,a 会进行右查询,就是查询 a 的值;b 会进行左查询,就是给 b 赋什么值。

关于作用域嵌套

当一个快或者函数嵌套在另一个快或者函数中时,就会发生作用域嵌套。这里有一个经典的作用域链查找

作用域链查找:其实就是从当前作用域出发,逐层的向上一级作用域查找,直到找到为止,或者找不到。

关于找不到报错的问题

  1. 执行 LHS 如果没有找到,就会在全局作用域中声明这个变量。
  2. 执行 RHS 如果没找到值,就会抛出 referenceError 的错误,表示在当前嵌套作用域的所有层级都找不到这个变量

关于作用域需要注意的点

一般作用的问题分为两种情况:

  1. 当前作用域中声明了和外层作用域相同的变量
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);

这种情况下,每部作用域的变量不会去覆盖全局变量,具体参考作用域查询规则

  1. 当前作用域没有声明同名变量
var i = 10;

function a() {
    i = 20;
    console.log(i); // 20
    console.log(this.i);
    console.log(i); // 20
}

a();
console.log(i);

这种情况下,内部作用域的变量引用的就是外部作用域中的变量,这时重新赋值会发生值得覆盖。