三个概念
引擎:从头到尾负责整个JavaScript程序的编译及执行过程
编译器(负责语法分析和代码生成等):
第一步:分词/词法分析。将由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元。
第二步:解析/语法分析。将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(AST)。
第三步:代码生成。将AST转换为可执行代码的过程。
作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
编译器查询变量的两种方式:LHS和RHS
“L”和“R”表示左和右,是一个赋值操作的左侧和右侧。
例如:let a = b
首先对b进行RHS,找到b,拿到它的值,然后就停止。再找到a,给他进行赋值操作,并不关心它之前的值。
准确来说,RHS并不是真正意义上的“赋值操作的右侧”,而是“非左侧”,例如:console.log(a),需要使用RHS查询到拿到a的值,但是连个等号都没有。
作用域嵌套
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,当在此前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(即全局作用域)为止。
考察如下代码:
function foo(a){
console.log(a + b)
}
var b = 2
foo(2) // 4
foo函数内部找不到b变量时,就会向上一级作用域查找(该例中就是全局作用域),找到就停止,找不到就报错。
引擎抛出的两种异常
对RHS来说,如果找到全局作用域还是没找到,就会抛出ReferenceError异常。如果找到了,但是试图对该变量进行不合理的操作,例如对一个非函数类型的变量进行函数调用,或者引用null或undefined类型的值中的属性,那么会抛出TypeError异常
对LHS来说,如果在“非严格模式”下,需要被赋值的变量找不到,就会在全局创建一个具有该名称的变量,不会报错。而在“严格模式”下,会抛出同RHS查询失败时类似的ReferenceError异常。
总结:ReferenceError同作用域判别失败相关,而TypeError则代表作用域判别成功了,但是对结构的操作是非法的或不合理的。