JavaScript 是实际上是一门编译语言。但 JavaScript 并没有多少时间可供编译,在大部分情况下,编译会发生在 JavaScript 代码执行前的几微秒时间内。所以 JavaScript 引擎用了各种办法(比如 JIT)来保证性能。
JIT是“Just In Time”的首字母缩写。每当一个程序在运行时创建并运行一些新的可执行代码,而这些代码在存储于磁盘上时并不属于该程序的一部分,这些可执行代码就是一个JIT。
1 LHS 与 RHS
要说清 JavaScript 的作用域,我们先从赋值操作说起。先来看一段赋值操作代码:
var a = b;
这段变量赋值操作,实际会执行两个动作:
- 编译器会判定该变量之前是否还未声明,如果之前没有声明过,则在当前作用域中声明变量;
- 然后在运行时,引擎会在作用域中查找该变量,如果能够找到就会对它赋值。
引擎查找变量,有两种类型,一种叫 LHS(Left Hand Side),另一种叫 RHS(Right Hand Side)。LHS 会试图找到变量本身(比如示例代码中的 a), 从而可以对其赋值;而 RHS 会查找某个变量的值,用于真正赋值操作(比如示例代码中的 b)。
RHS 类型查找有多种变体,比如以下这段代码:
function print(x) {
console.log(x);
}
print(1);
运行结果:
1
- 这里的 print(...) 就是一种 RHS 类型查找,引擎会去寻找这个函数,然后调用它。
- 实际上,这里还有一个赋值操作,即 x = 1,实际上使用的是 LHS 类型查找。
2 作用域链
作用域是根据名称查找变量的一套规则。 而在实际情况中, 经常会发生作用域嵌套现象,比如一个块或函数嵌套在另一个块或函数中。在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找该变量,直到找到或抵达最外层的全局作用域为止。这些一层又一层的作用域,就构成了作用域链。
我们可以把作用域链想象成一栋大楼,第一层表示当前执行作用域,第二层表示嵌套在外层的作用域,以此类推。大楼的顶层表示全局作用域 。LHS 和 RHS 都会先在当前楼层进行查找,如果没有找到,会前往上一层,如果还是没有找到就继续向上,以此类推。此过程会一直持续到顶层,即全局作用域。
3 作用域异常
如果在任何作用域中都无法找到所需要的变量,LHS 和 RHS 对该场景的处理行为并不相同。RHS 查询会抛出 ReferenceError 异常。而 LHS 在非严格模式(ES5 中引入严格模式)下,会自动在全局作用域中创建一个具有该名称的变量。严格模式下,也会抛出 ReferenceError 异常。
如果 RHS 查询找到了一个变量,但是对其进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或着引用 null 或 undefined 类型的值中的属性等等,这时引擎会抛出 TypeError。 所以,ReferenceError 异常表示作用域中没有找到所需要的变量,而 TypeError 表示已找到,但操作非法。
总结如下:
- 作用域是一套用于确定在什么地方以及如何查找所需变量的一套规则。 LHS 查找的目的是对变量进行赋值;RHS 查找的目的是获取变量的值 。
- 赋值操作符会导致 LHS 查询。= 操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作 。* LHS 和 RHS 查询都会在当前执行作用域中开始,如果没有找到所需的变量,就会向嵌套的上级作用域中继续查找符,直到抵达全局作用域为止。
- 到达全局作用域后,如果还是未找到, RHS 查找会抛出 ReferenceError 异常。非严格模式下的 LHS 会自动隐式地创建一个全局变量,而严格模式下的 LHS,一样会抛出 ReferenceError 异常。
网友评论