Skip to content

第一章:启程:JavaScript 解析器概览

欢迎来到 mini-acorn 的世界!

在正式开始构建我们自己的 JavaScript 解析器之前,让我们先花些时间,从一个更高、更宏观的视角来理解一个问题:什么是解析器?我们为什么需要它?

1. 前端工具链的“黑盒”

作为现代前端开发者,我们每天都在与各种强大的工具打交道:

  • Babel:将我们写的 ESNext、TypeScript、JSX 代码转换为浏览器可以理解的 JavaScript。
  • ESLint:检查我们的代码风格,发现潜在的错误。
  • Prettier:一键格式化我们的代码,统一团队风格。
  • Webpack/Vite:分析模块依赖,将整个项目打包成最终的产物。

这些工具极大地提升了我们的开发效率和代码质量。但你是否想过,它们是如何“读懂”并“操作”我们的代码的?

无论是代码转换、风格检查还是依赖分析,这些工具工作的第一个步骤,也是最重要的共同基础,就是解析(Parsing)。它们内部都包含一个解析器(Parser),这个解析器会将我们写的源代码字符串,转换成一种计算机更容易理解和处理的结构化表示——抽象语法树(Abstract Syntax Tree, AST)

这个从“代码字符串”到“AST”的过程,就是我们本书要探索的核心。通过亲手构建一个解析器,我们将彻底揭开这些工具的神秘面纱。

2. 解析器的两阶段工作

一个解析器的工作通常分为两个核心阶段:词法分析(Lexical Analysis)语法分析(Syntactic Analysis)

让我们以一行非常简单的代码为例,看看这两个阶段分别做了什么:

javascript
let a = 1;

2.1. 阶段一:词法分析 (Lexical Analysis)

词法分析,也常被称为“分词”(Tokenizing)。在这个阶段,解析器会逐一读取代码字符串,然后根据语言的词法规则,将字符串分解为一系列有意义的、不可再分的最小单元。这些单元被称为 Token

对于 let a = 1; 这行代码,词法分析器会输出一个 Token 数组,可能像下面这样:

json
[
  { "type": "Keyword", "value": "let" },
  { "type": "Identifier", "value": "a" },
  { "type": "Punctuator", "value": "=" },
  { "type": "Numeric", "value": "1" },
  { "type": "Punctuator", "value": ";" }
]

你可以把 Token 想象成英语句子中的“单词”。词法分析就像是把一个句子拆分成一个个独立的单词,并识别出它们的词性(名词、动词、形容词等)。值得注意的是,像空格、换行这类字符通常在这个阶段被忽略,因为它们不影响代码的语法结构。

2.2. 阶段二:语法分析 (Syntactic Analysis)

语法分析,也就是我们通常狭义上说的“解析”(Parsing)。在这个阶段,解析器会接收词法分析产出的 Token 序列,然后根据语言的语法规则,将这些扁平的“单词”组装成一个能够体现程序逻辑的、树形的结构。这个结构就是抽象语法树(AST)

对于上面的 Token 序列,语法分析器会构建出如下的 AST:

json
{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": { "type": "Identifier", "name": "a" },
          "init": { "type": "Literal", "value": 1 }
        }
      ],
      "kind": "let"
    }
  ]
}

这棵树精确地描述了原始代码的结构:这是一个程序(Program),它包含一个变量声明语句(VariableDeclaration),声明的类型是 let,声明了一个变量 a,并用字面量 1 对其进行了初始化。

至此,解析器的核心使命便完成了。它成功地将无结构的文本,转换成了结构化的树。

3. 为什么 AST 如此重要?

AST 是所有代码分析和转换工具的基石。一旦源代码被转换成 AST,我们就可以对其进行各种有趣且强大的操作:

  • 代码转换:Babel 遍历 AST,找到代表 ES6+ 语法的节点(如 ArrowFunctionExpression),然后将它们替换成功能等价的 ES5 节点(如 FunctionExpression)。
  • 静态分析:ESLint 遍历 AST,检查节点是否符合预设的规则。例如,它可以检查 VariableDeclaration 节点的 kind 属性,如果发现是 var,就发出一个警告。
  • 代码格式化:Prettier 也是通过解析代码到 AST,然后根据自己的风格规则,重新将 AST 生成为格式化好的代码字符串。

可以说,AST 是连接源代码和上层工具的桥梁

4. 总结与练习

在本章,我们对 JavaScript 解析器建立了一个宏观的认知。我们知道了:

  • 解析器是 Babel、ESLint 等工具的核心,它的作用是将源代码字符串转换为 AST。
  • 这个过程分为词法分析(代码 → Tokens)和语法分析(Tokens → AST)两个阶段。
  • AST 作为代码的结构化表示,是所有代码分析和转换工作的基础。

现在,是时候动手体验一下了。

练习

  1. 工具使用:打开在线工具 AST Explorer。在左上角选择 “JavaScript” 和 “acorn” 作为解析器。在左侧代码框中输入 const name = "world";,然后仔细观察右侧生成的 AST 结构。尝试点击 AST 的不同节点,看看它如何与左侧的代码高亮联动。
  2. 概念思考:为什么 AST 被称为“抽象”语法树?它到底“抽象”掉了源代码中的哪些信息?(提示:回想一下词法分析阶段我们忽略了什么。)
  3. 应用场景:除了 Babel 和 ESLint,你还能想到哪些你日常使用的工具或功能,其底层可能也用到了 AST?(提示:想想代码压缩、代码高亮、编辑器的自动补全……)
第一章:启程:JavaScript 解析器概览 has loaded