作用域是什么
编译原理
传统编译语言的流程中,程序中的一段代码在执行之前会经历三个步骤,统称为“编译”。
- 分词/词法分析
将字符组成的字符串分解成有意义的代码块。 - 解析/语法分析
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为“抽象语法树(Abstract Syntax Tree,AST)”。 - 代码生成
将AST转换为可执行的代码,这个过程与语言和目标平台息息相关。
JavaScript引擎不会有大量时间用来进行优化,因为它的编译过程不是发生在构建之前的,而是发生在代码执行前的几微妙(甚至更短)的时间内。
简单地说,任何JavaScript代码片段在执行前都进行编译(通常就在执行前)。
理解作用域
编程语言最基本功能就是可以存储变量当中的值,后续还可以继续访问或进行修改。语言需要设计一套规则来存储变量,方便查找这些变量,这套规则就成为作用域。
变量的赋值操作会执行两个动作,首先编译器会在当前作用域中生成一个变量(如果之前没有生成过),然后在运行时引擎会在作用域中查找该变量,如果能找到就会对它赋值,否则会抛出一个异常!
LHS和RHS查询
LHS是查找变量容器本身,RHS是要得到变量的值。下面看一个例子:
|
|
- foo函数调用需要对foo进行RHS查询,引擎会问作用域是否有foo。
- 2赋值给foo形参a之前,需要对a进行LHS查询。
- console对象进行的是RHS查询,并且检查是否有一个叫log的方法。
- 最后对a进行RHS查询得到2,程序输出。
作用域嵌套
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域嵌套。因此,在当前作用域中无法找到某个变量时,引擎会在外层嵌套的作用与众继续查找,知道找到该变量,或抵达最外层的作用域为止。
异常
为什么区分LHS和RHS是一件重要的事情?
因为在变量还没有声明的情况下,这两种查询的行为是不一样的。考虑如下代码:
|
|
第一次对b进行RHS查询时是无法找到该变量的。也就是说,这是一个“未声明”的变量。
如果RHS查询在所有嵌套的作用域中都找不到所需的变量,引擎会抛出ReferenceError异常。
而当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域会创建一个具有该名称的变量,并将其返还给引擎(非严格模式下)。
严格模式下LHS对找不到变量的处理方式和RHS一样,都会抛出ReferenceError异常。
ReferenceError同作用域判别失败相关,TypeError是作用域判别成功了,但是对结果的操作是非法不合理的,如引用null或undefined中的属性值。