1.编译原理
1.1 在传统编译语言的流程中,程序中的一段代码在执行前,都要经历以下三个步骤:
- 分词/词法分析:简单来讲,就是将字符组成的字符串分解成(对编程语言来讲)有意义的代码块,即词法单元。
- 解析/语法分析:将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树(AST,Abstract Syntax Tree 抽象语法树)。
- 代码生成:将AST转换成可被执行的代码的过程。
对于编译原理,了解到这里差不多,若时间充裕可另行研究。
1.2 JS与其他语言编译时的不同之处:
JS的编译大部分情况下发生在代码执行的前几微秒(甚至更短!),所以JS的优化显得比其他语言更为重要。这主要归功于JS引擎用尽各种方法(例如JIT,可以延迟编译甚至实施重编译)来优化JS,保证性能最佳。
2.理解作用域
2.1 跟作用域有关的几个名词
- 引擎: 负责JS从头到尾整个的编译及执行过程
- 编译器: 负责语法分析、代码生成等
- 作用域: 负责收集并维护由所有声明的标识符组成的一系列查询,并实施一套严格的规则,确定当前执行的代码对这些标识符的访问权限
2.2 举个栗子:
var a = 2;
按照之前的理解,编译器会将这段程序代码分解成词法单元(var,a,=,2,;),然后将词法单元流解析成一个树结构,之后进行代码生成。
但现在新增了引擎和作用域的概念之后,有几处不一样了(或者说过程被丰富了):
- 引擎会将上述代码看成两个完全不同的声明:
- var a,声明变量a,由编译器在编译时处理
- a = 2,给变量a赋值2,则由引擎在运行时处理
- 作用域则会影响编译器在编译过程中对变量a的声明:
- 首先,编译器遇到var a,会先询问作用域在当前作用域下是否有同名变量a,如果有则忽略此条声明;否则它就在当前作用域下声明一个新的变量,并命名为a
- 接下来编译器会为引擎生成执行代码,用来处理a = 2的赋值操作。引擎运行时首先会询问作用域,当前作用域下是否有变量a,如果有则使用该变量,如果没有则向上查找改变量。
- 如果引擎最终找到了该变量,就把2赋值给它,如果没有找到,则抛出异常。
总的来说,变量的赋值操作会执行两个动作,首先编译器会在当前作用域声明一个变量(如果当前作用域没有声明过该变量),然后引擎在运行时在作用域中查找该变量,如果找到就赋值。
2.3 引擎查询—LHS和RHS
a=2;
代码的执行由作用域协助,引擎进行查询并赋值来完成。引擎执行怎样的查找,影响最终的查找结果。而引擎查找的类型有LHS查询和RHS查询。
LHS查询:
- 最直白的理解:出现在赋值符号左侧的变量执行LHS查询
- 更准确的理解:试图找到变量的容器本身
- 概念上的理解:赋值操作的目标是“谁”,即对“谁”进行赋值
RHS查询:
- 最直白的理解:出现在赋值符号非左侧(不一定是右侧!)的变量执行RHS查询
- 更准确的理解:简单地找到变量的值
- 概念上的理解:赋值操作的源头,即把“谁”赋值给目标
总结:如果查找的目的是对变量进行赋值,那么启用LHS查询;如果查找的目的是为了取得变量的值,那么启用RHS查询。
!注意:
function foo(a) { ... }
不适合理解为:var foo;foo=function(a) {...}
,原因如下:
编译器在代码生成的同时会同步处理声明和值的定义,即在编译阶段上述代码中的变量foo就会被声明且函数本身也会被定义,引擎运行时就不会也不用对上述代码进行处理。若按照之前的分步理解,则会缺少将函数体function(a){...}
赋值给变量foo这一步操作(引擎不会单独开一个线程来处理此事,因为编译器已处理完毕的情况下没有必要)。
2.4 小测验(Page10)
找出其中所有的LHS查询(3处)和RHS查询(4处):
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
3.作用域嵌套
3.1 定义
当一个块或者函数嵌套在另一个块或函数中时,就会发生作用域的嵌套。
3.2 遍历嵌套作用域链的规则
引擎从当前作用域开始查找,如果没找到,就会向上一级(外面那一层“父级”作用域)继续查找,当抵达最外层的全局作用域时,无论找没找到,查找都会终止。
!注意: 遍历嵌套作用域时需要区分当前执行的是LHS查询还是RHS查询(具体原因见下述4)
4.引擎执行两种查询时的异常处理
4.1 LHS异常
当引擎执行LHS查询时,如果在全局作用域下都未找到目标变量时且程序运行在非“严格模式” 下,会在全局作用域中创建一个具有该名称的变量,并将其返还给引擎。
但在ES5的严格模式下,它禁止自动或隐式地创建全局变量,因此在引擎执行LHS查询失败时,会抛出ReferenceError异常。
4.2 RHS异常
如果引擎在执行RHS查询时在所有的嵌套作用域内都无法找到该变量,引擎就会抛出ReferenceError异常;如果找到该变量,但对该变量进行了错误或不合理的操作(如对一个非函数类型的值进行调用,或者引用null或undefined类型的值中的属性),引擎就会抛出TypeError异常。
不成功的RHS查询会抛出ReferenceError异常。不成功的LHS查询会导致自动隐式地创建一个全局变量(非严格模式),或者抛出ReferenceError异常(严格模式)
4.3 总结
换言之,ReferenceError与作用域的判别失败有关,TypeError则代表作用域判别成功,但对结果进行了不合理或非法的操作。