作用域是什么?

几乎所有的编程语言最基本的功能都是变量的存储和访问,正是这种变量的存储和访问的能力将状态带给了程序,而作用域的就是为了寻找和访问变量设计的一套规则。

理解作用域

在理解作用域前我们先来看一下程序的编译原理,大多数的程序的编译原理大概三个步骤:

分词/词法分析

这个过程会将有字符串组成的代码块,分解成一个一个的词法单元。例如var a = 2 ;会被分解为var,a,=,2,;

解析/语法分析

这个过程会将分解的词法单元进行组合,组成一个抽象语法树林(AST,Abstract Syntax Tree)

代码生成

会将抽象语法树转换成转为可以执行的代码,转为一组机器指令进行执行

有了上面的编译原理,我们就可以看看如何把var a = 2;这段代码如何在机器中进行解释

var a =2;在分词后重组为AST,语言编辑执行分为两步

  1. var a编译器询问作用域中的a变量,如果存在则返回变量地址,如果不存在则创建一个a变量
  2. a =2,在当前作用与中查询a变量,如果存在则返回变量地址,否则则创建一个,将数值2存储到变量a对应的地址中。

LSH 和 RSH

上面我们大概说了程序会如何配合作用域处理var a = 2;代码,下来我们来看看查询时所用的两种查询:

  • LSH
  • RSH

LSH查询是指目标地址查询,它一般出现在赋值运算符的左侧,它用来查询变量的存储地址;RSH查询是指源值查询,它一般出现在赋值运算符的右侧,它用来获取变量的源值

下面我们看一个例子:

1
2
3
4
5
function foo(a){
console.log(a)
}
foo(2)

我们分析一下上面所有到的查询:

  1. foo对象使用RSH查询,获取foo函数的值
  2. 对隐式a=2中的a使用LSH查询,在当前作用域中请求变量a地址,不存在则创建
  3. console对象使用RSH查询,获取console对象值
  4. 对变量a使用RSH查询获取值并传递给console.log函数

作用域链

我们通过之前的一些例子理解作用域,可以每次进行查询时,都会向当前作用域进行LHSRHS请求,可以把当前作用域想想成一个变量数组,如果当前作用域的变量数组中不存在该变量,那么它会想上级作用域请求,在上级的作用域中进行查询,有则返回,没有则依次向上,这种作用域模型成为作用域链,可以把它理解成一个树的数据模型,有唯一一个根节点,是根作用域,代码在当前执行作用域节点进行请求,如果找到则返回,如果没有则依次向上,直到请求到根节点,这时可以你找到可你的变量,也可能没有找到,无论如何查找过程都将停止。

连接嵌套的作用域,形成了作用域链,完成了变量查找和存储的规则。

异常

在执行LHS和`RHS``的过程中主要有两种异常:

  • RenferenceError
  • TypeError

RenferenceError异常主要是出现在RHS查询过程中没有在可查找的作用域中找到相应的变量,在严格模式中LHS也会触发RenferenceError异常

TypeError则是如果查询成功,但是对该对象执行不合理的操作或引用空类型值中的属性时,引擎会抛出该异常。

ReferenceError表示作用域内查询失败,而TypeError则表示作用域内判别成功。