作用域是什么

作用域是什么

编译原理

传统编译语言的流程中,程序中的一段代码在执行之前会经历三个步骤,统称为“编译”。

  1. 分词/词法分析
    将字符组成的字符串分解成有意义的代码块。
  2. 解析/语法分析
    这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为“抽象语法树(Abstract Syntax Tree,AST)”。
  3. 代码生成
    将AST转换为可执行的代码,这个过程与语言和目标平台息息相关。

JavaScript引擎不会有大量时间用来进行优化,因为它的编译过程不是发生在构建之前的,而是发生在代码执行前的几微妙(甚至更短)的时间内。

简单地说,任何JavaScript代码片段在执行前都进行编译(通常就在执行前)。

理解作用域

编程语言最基本功能就是可以存储变量当中的值,后续还可以继续访问或进行修改。语言需要设计一套规则来存储变量,方便查找这些变量,这套规则就成为作用域。

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中生成一个变量(如果之前没有生成过),然后在运行时引擎会在作用域中查找该变量,如果能找到就会对它赋值,否则会抛出一个异常!

LHS和RHS查询

LHS是查找变量容器本身,RHS是要得到变量的值。下面看一个例子:

1
2
3
4
function foo(a){
console.log(a);
}
foo(2);
  1. foo函数调用需要对foo进行RHS查询,引擎会问作用域是否有foo。
  2. 2赋值给foo形参a之前,需要对a进行LHS查询。
  3. console对象进行的是RHS查询,并且检查是否有一个叫log的方法。
  4. 最后对a进行RHS查询得到2,程序输出。

作用域嵌套

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域嵌套。因此,在当前作用域中无法找到某个变量时,引擎会在外层嵌套的作用与众继续查找,知道找到该变量,或抵达最外层的作用域为止。

异常

为什么区分LHS和RHS是一件重要的事情?

因为在变量还没有声明的情况下,这两种查询的行为是不一样的。考虑如下代码:

1
2
3
4
5
function foo(a){
console.log(a + b);
b = a;
}
foo(2);

第一次对b进行RHS查询时是无法找到该变量的。也就是说,这是一个“未声明”的变量。

如果RHS查询在所有嵌套的作用域中都找不到所需的变量,引擎会抛出ReferenceError异常。

而当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域会创建一个具有该名称的变量,并将其返还给引擎(非严格模式下)。

严格模式下LHS对找不到变量的处理方式和RHS一样,都会抛出ReferenceError异常。

ReferenceError同作用域判别失败相关,TypeError是作用域判别成功了,但是对结果的操作是非法不合理的,如引用null或undefined中的属性值。