编译原理
在介绍 JS 的作用域之前,先介绍一下编译原理。
JavaScript引擎进行编译的步骤和传统编译语言比较相似,但在某些环节比预想的复杂。
在传统的编译语言中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。
- 分词/词法分析:将有字符组成的字符串分解成(对编程语言来说)有意义的代码块。
- 解析/语法分析:将词法单元流(数组)转换成一个有元素逐级嵌套所形成的代表了程序与法结构的树。这个数被称为“抽象语法树”(Abstract Syntax Tree, AST)。
- 代码生成:将 AST 转换为可执行代码的过程。
理解作用域
如 var a = 2,变量的复制操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能找到就对它进行赋值。
LHS 和 RHS
引擎会为变量 a 进行 LHS 查询,另外一个查询的类型叫做 RHS。含义分别为 Left Hand Side 和 Right Hand Side。
即变量出现在复制操作左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。RHS 查询与简单的查找某个变量的值无异,LHS 则试图找到变量的容器本身。从而可以对其赋值。准确的说,所有“非左侧”都是 RHS。
1 | console.log(a); |
其中对 a 的引用是一个 RHS 引用
1 | a = 2; |
这里对 a 的引用是一个 LHS 引用
1 | function foo(a) { |
最后一行 foo(2) 函数的调用需要对 foo 进行 RHS 引用
代码中隐式的 a = 2 的操作需要进行一次 LHS 查询
将得到的值传给 console.log(),需要对 a 进行一次 RHS 引用
作用域嵌套
作用域是根据名称查找变凉的一套规则。当在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,知道找到该变量或抵达全局作用域为止。
LHS 和 RHS 引用都会在当前作用域进行查找,如果没有找到,就往上一层作用域,直到找到或者抵达全局作用域后停止查找。
如果 RHS 查询在所有嵌套的作用域中都找不到所需的变量,引擎就会抛出 ReferenceError 异常;如果 LHS 查询在全局作用域中也无法找到目标变量,则会隐式地在全局作用域创建这个变量(非严格模式下)并作为这次 LHS 查询的目标,或者抛出 ReferenceError。
如果试图对 RHS 查询到的变量的值进行不合理的操作,如调用非函数类型的值或引用 null 或 undefined 中的属性,则会抛出 TypeError。
ReferenceError 和作用域判别失败有关, TypeError 则是判别成功了,但是对结果的操作是非法的。