作用域那些事儿~

799 阅读5分钟

理解作用域

我们将值存储在变量里面,这些变量储存在哪里?最重要的是 : 当我们要使用的时候,如何找到它?

这些问题说明需要设计一套良好的规则来对这些变量进行存储管理,并且之后可以方便的找到这些变量。而这套规则被称为作用域

所以说,作用域是一套查询变量的规则,说直白一点就是:作用域是规则

那么作用域是规则,怎么体现出规则的?下面先看点别的东西,然后就以一个简单的例子来帮助我们理解作用域。

编译原理

通常一段程序在执行之前都会经历三个步骤,这三个步骤统称为编译

  • 分词/词法解析
    在这个阶段会将字符组成的字符串进行分解成为有意义的代码块,这些代码块被称为词法单元,就好比如说 var a = 2;在这个阶段会被分解成为下面这些词法单元:var , a , = , 2 , ;
  • 解析/语法分析
    在这个阶段会将词法解析后的一些词法单元流转换成一个AST树(抽象语法树)。
  • 代码生成
    将AST树转换成可执行的代码的过程。

上面的三个过程和Vue的模板编译的过程感觉有点相似。
template -> AST -> render()。

参与人员

  • 引擎:从头到尾负责javascript程序的编译和执行过程
  • 编译器:负责语法分析和代码生成等
  • 作用域:负责收集并维护所有的声明标识符(变量)组成的一系列的查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

对话

就拿下面这句简单的代码来说,

	var = 2;

当执行这段程序之前,编译器会将var=2;这串字符串分解成为词法单,然后组成AST,但在代码生成的阶段,编译器做了下面的处理:

  1. 遇到var a,编译器会向作用域询问,是否有一个该名称的变量存在于同一个作用域的集合中,如果是,则会忽略该声明,继续进行编译,否则就会在当前作用域的集合当中声明一个新的变量,并且命名为a。
  2. 接下来生成运行时所需要的代码,这些代码是被用来处理 a = 2这个赋值操作的,引擎运行的时候,首先会询问作用域,是都存在一个叫做a的变量,如果是,引擎就会使用这个变量,如果不是,则继续查找(作用域链)。如果找到了,就赋值,没有找到,就抛出一个错误。

LHS和RHS

为了进一步理解,我们需要一些编译器的术语
所谓LHS和RHS就是询问作用域时的类型,LHS就是变量在赋值操作的左边,RHS就是在右边(取值)。
考虑下面代码:

console.log(a);

这里对a进行了一次RHS,因为这里a没有被赋值,相对应的是需要取值,才能传递给console.log()

相比之下,例如:

a = 2;

这里对a进行了一次LHS,因为我们不关心a的值是什么,只想要知道有没有a (为=2找到一个赋值的目标)。 为什么要区分LHS和RHS呢? 因为在变量未声明的情况下,这两种查询是不一样的:

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

第一次对b进行RHS查询的时候是无法找到该变量的,也就是说这是一个未声明的变量,无法在其相关的作用域中找到。如果RHS没找到的话,那么就会抛出ReferenceError异常。

相比之下,当引擎执行LHS查询的时候,如果在相关作用域下没有找到目标变量,那么就会在全局作用域中添加这个变量,并将其返回给引擎(非严格模式)。这就是为什么没有用var 声明的变量会被添加到全局作用域里面的原因!
如果RHS查询找到了这个变量,但是对这个变量进行一些不正当的操作,例如对一个非函数类型的值进行函数调用的时候,那么就会抛出TypeError异常。

作用域嵌套(作用域链)

当一个块或者函数嵌套在另一个块或者函数中的时候,就发生了作用域的嵌套,因此在当前作用域中无法找到某个变量的时候,引擎就会在外层嵌套的作用域中继续查找,知道找到该变量,或者抵达全局作用域为止。

function foo(){
    var a =10;
    function bar(){
       console.log(a);//10
   }
}

foo()

在bar函数里面,对a执行的RHS没有找到,那么就去外层foo的作用域里面找。

作用域小结

作用域是一套规则,用于确定在何处以及如何查找变量(标识符),如果查找的目标是对变量进行赋值,那么就会使用LHS查询,如果是获取变量的值,那么就会使用RHS查询。不成功的RHS查询抛出ReferenceError异常,不成功的LHS查询,(非严格模式下)会在全局自动创建一个同名变量并返回给引擎,严格模式下抛出ReferenceError异常。

通过引入LHS和RHS这两种向作用域询问的概念,应该就比较容易理解作用域是一套规则了。