Skip to main content

编译原理

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

分词/词法分析

将由字符组成的字符串分解为(对编程语言来说)有意义的代码块,这些代码块被称为此法单元。如将var a = 2分解为vara=2;

解析/词法分析

将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,称为“抽象语法树”(Abstract Syntax Tree,AST)。

代码生成

将 AST 转换可执行代码的过程。简单来说就是将 AST 转化为一组机器指令。

任何 JS 代码片段在执行前都要进行编译(通常就在执行前,所需时间很短)。

理解作用域

演员表

  • 引擎

    从头到尾负责整个 JavaScript 程序的编译及执行过程。

  • 编译器

    负责语法分析及代码生成等脏活累活。

  • 作用域

    负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前代码对这些标识符的访问权限。

处理过程

我们来分析一下对于var a = 2,引擎和它的朋友们是如何协同工作的。

  1. 遇到var a,编译器会询问作用域是否存在一个名称为 a 的变量在同一作用域中。如果是,编译器会忽略该声明,继续编译;如果否,就让作用域在当前作用域声明一个新的变量 a。
  2. 编译器为引擎生成运行时所需的代码。引擎询问作用域在当前作用域中是否存在变量 a,如果存在使用这个变量;如果不存在,引擎会继续查找该变量。
  3. 如果引擎最后找到变量 a,就会把 2 赋值给它;否则,就抛出一个异常。

查询变量的方式

  • LHS 查询 : 变量出现在赋值操作左侧时进行 LHS 查询,给函数参数赋值也是一种隐式 LHS 查询。
  • RHS 查询:变量出现在赋值操作右侧时进行 RHS 查询,可以理解为“得到某某的值”,比如,console.log(a)、foo(2),都进行了 RHS 查询,查询了变量 a 和 foo 函数。

函数声明不是 LHS 查询和赋值,函数名和函数之间的关联是在作用域的开头自动设置的

作用域嵌套

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

异常

在进行 RHS 查询时,如果在全局作用域中也无法找到目标变量,引擎就会抛出ReferenceError异常

LHS 查询失败时,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎(非严格模式下)

严格模式下,LHS 查询失败同样会抛出ReferenceError异常。

如果 RHS 查询成功,但是对这个变量进行不合理的操作时,比如引用nullundefined的属性时,那么引擎就会抛出TypeError异常。