根据你与各种编程语言打交道的水平不同,这也许是不证自明的,或者这也许令人吃惊,尽管 JavaScript 一般被划分到“动态”或者“解释型”语言的范畴,但是其实它是一个编译型语言。它 不是 像许多传统意义上的编译型语言那样预先被编译好,编译的结果也不能在各种不同的分布式系统间移植。

    但是无论如何,JavaScript 引擎在实施许多与传统的语言编译器相同的步骤,虽然是以一种我们不易察觉的更精巧的方式。

    在传统的编译型语言处理中,一块儿源代码,你的程序,在它被执行 之前 通常将会经历三个步骤,大致被称为“编译”:

    1. 分词/词法分析: 将一连串字符打断成(对于语言来说)有意义的片段,称为 token(记号)。举例来说,考虑这段程序:var a = 2;。这段程序很可能会被打断成如下 token:vara=2,和 ;。空格也许会被保留为一个 token,这要看它是否是有意义的。

      注意: 分词和词法分析之间的区别是微妙和学术上的,其中心在于这些 token 是否以 无状态有状态 的方式被识别。简而言之,如果分词器去调用有状态的解析规则来弄清a是否应当被考虑为一个不同的 token,还是只是其他 token 的一部分,那么这就是 词法分析

    2. 解析: 将一个 token 的流(数组)转换为一个嵌套元素的树,它综合地表示了程序的语法结构。这棵树称为“抽象语法树”(AST —— Abstract Syntax Tree)。

      var a = 2; 的树也许开始于称为 VariableDeclaration(变量声明)顶层节点,带有一个称为 Identifier(标识符)的子节点(它的值为 a),和另一个称为 AssignmentExpression(赋值表达式)的子节点,而这个子节点本身带有一个称为 NumericLiteral(数字字面量)的子节点(它的值为2)。

    3. 代码生成: 这个处理将抽象语法树转换为可执行的代码。这一部分将根据语言,它的目标平台等因素有很大的不同。

      所以,与其深陷细节,我们不如笼统地说,有一种方法将我们上面描述的 var a = 2; 的抽象语法树转换为机器指令,来实际上 创建 一个称为 a 的变量(包括分配内存等等),然后在 a 中存入一个值。

      注意: 引擎如何管理系统资源的细节远比我们要挖掘的东西深刻,所以我们将理所当然地认为引擎有能力按其需要创建和存储变量。

    和大多数其他语言的编译器一样,JavaScript 引擎要比这区区三步复杂太多了。例如,在解析和代码生成的处理中,一定会存在优化执行效率的步骤,包括压缩冗余元素,等等。

    所以,我在此描绘的只是大框架。但是我想你很快就会明白为什么我们涵盖的这些细节是重要的,虽然是在很高的层次上。

    其一,JavaScript 引擎没有(像其他语言的编译器那样)大把的时间去优化,因为 JavaScript 的编译和其他语言不同,不是提前发生在一个构建的步骤中。

    对 JavaScript 来说,在许多情况下,编译发生在代码被执行前的仅仅几微秒之内(或更少!)。为了确保最快的性能,JS 引擎将使用所有的招数(比如 JIT,它可以懒编译甚至是热编译,等等),而这远超出了我们关于“作用域”的讨论。

    为了简单起见,我们可以说,任何 JavaScript 代码段在它执行之前(通常是 刚好 在它执行之前!)都必须被编译。所以,JS 编译器将把程序 var a = 2; 拿过来,并首先编译它,然后准备运行它,通常是立即的。