DOM树就是AST?不是,我错了😭😭😭

630 阅读4分钟

人类的文本,机器的树:AST为什么是编程世界的翻译官

记录我从“DOM树就是AST”到被打脸,最终认清“编译AST vs 运行时对象树”的完整顿悟之旅。

从一次代码调试说起

那天,我在写Babel插件,第一次真正撞上AST:

// 我写的代码
const greeting = "Hello " + "World";

// 对应的AST(简化)
{
  type: "VariableDeclaration",
  declarations: [{
    type: "VariableDeclarator",
    id: { type: "Identifier", name: "greeting" },
    init: {
      type: "BinaryExpression",
      operator: "+",
      left: { type: "Literal", value: "Hello " },
      right: { type: "Literal", value: "World" }
    }
  }]
}

我当场困惑:这么简单的语句,为什么要转换成如此复杂的树结构?这个疑问开启了我的探索之旅。

突破认知:原来我一直在用AST!

深入理解后,我发现前端开发者其实每天都在使用AST,只是不知道它叫这个名字:

  • JS/JSX → Babel的AST
  • CSS → PostCSS的AST
  • HTML → parse5的AST

它们都是源代码的静态骨架,能被JSON化、被插件遍历、被无损写回磁盘。

认知修正:DOM树真不是AST!

正当我得意地把DOM树贴上"HTML的AST"标签时,现实给我上了一课:

// 浏览器DOM
document.body instanceof HTMLBodyElement   // true
JSON.stringify(document.body)              // ❌ 循环引用报错

// PostCSS输出的才是AST
npx postcss input.css --parser postcss-parser --stringifier json
// ✅ 干净的JSON,能序列化、能遍历

关键区别

  • DOM/CSSOM是运行时宿主对象,带方法、带状态,与文档生命周期共存
  • AST是构建时纯数据结构,只存在于编译阶段,完成使命后就被释放

真正的HTML AST(parse5)长这样:

{
  "nodeName": "body",
  "tagName": "body", 
  "attrs": [{ "name": "class", "value": "main" }],
  "childNodes": [ ... ]   // 纯数据,无方法

AST的"超能力":编程世界的标准集装箱

AST最强大的地方在于它像国际海运的标准集装箱,消除了各种语法"方言"的差异。

没有AST的噩梦:每个工具都要为每种语法编写解析器:

  • Babel解析器:理解JSX、TS、ES2023...
  • ESLint解析器:理解JSX、TS、ES2023...
  • 重复劳动,维护灾难!

有AST的美好现实

各种语法 → AST标准格式 → 各个工具处理AST
         ↓
  专门的解析器团队维护

三大"树"对比(修正版)

树类型是否AST生命周期可否JSON化操作示例
JS AST(Babel)构建瞬间path.replaceWith()
HTML AST(parse5)构建瞬间visit(node)
CSS AST(PostCSS)构建瞬间walkDecls()
DOM页面卸载前document.body.style
CSSOM页面卸载前sheet.insertRule()

为什么需要AST?人类与机器的根本矛盾

人类的偏好:线性文本 我们喜欢一行行写代码,因为:

  • ✅ 符合阅读习惯
  • ✅ 编写简单直观
  • ✅ 易于版本管理
// 人类友好的方式
function calculateTotal(price, quantity) {
  return price * quantity * 0.9; // 打9折
}

机器的需求:树形结构
计算机需要树形结构,因为:

  • ✅ 易于分析语法关系
  • ✅ 方便进行优化转换
  • ✅ 支持高效遍历查询
// 机器友好的AST结构
{
  type: "FunctionDeclaration",
  name: "calculateTotal", 
  params: ["price", "quantity"],
  body: {
    type: "ReturnStatement",
    argument: {
      type: "BinaryExpression",
      operator: "*",
      left: {
        type: "BinaryExpression",
        operator: "*", 
        left: { type: "Identifier", name: "price" },
        right: { type: "Identifier", name: "quantity" }
      },
      right: { type: "Literal", value: 0.9 }
    }
  }
}

AST就是这个矛盾的完美解决方案。

前端框架中的AST魔法应用

Vue的模板编译

<template> → 模板AST → 优化(静态提升)→ render函数

React的JSX转换

// 开发时写的JSX
const element = <div className="title">Hello {name}</div>;

// 通过AST转换为:
const element = React.createElement(
  "div", 
  { className: "title" }, 
  "Hello ", 
  name
);

实战价值:用AST思想解决工程问题

案例:自动化代码重构 团队从MobX切换到Redux,手动修改几百个文件不现实:

// 基于AST的自动化重构
const ast = parser.parse(sourceCode);
traverse(ast, {
  ClassDeclaration(path) {
    if (isMobXStore(path.node)) {
      convertToReduxReducer(path);  // 自动转换
    }
  }
});

案例:自定义团队规范

// ESLint规则基于AST实现
module.exports = {
  create(context) {
    return {
      MemberExpression(node) {
        if (node.object.name === 'console') {
          context.report({
            node,
            message: '请使用logger代替console',
            fix(fixer) {
              return fixer.replaceText(node.object, 'logger');
            }
          });
        }
      }
    };
  }
};

AST的性能优势:为什么"多此一举"反而更快

文本操作的陷阱 vs AST的精准

// 文本操作:用正则查找函数名(容易误匹配)
const functionNames = code.match(/(function|\w+)\s*(\w+)\s*[=(]/g);

// AST操作:精准识别各种函数定义
traverse(ast, {
  FunctionDeclaration(path) {
    names.push(path.node.id.name);  // 普通函数
  },
  VariableDeclarator(path) {
    if (path.node.init?.type === 'ArrowFunctionExpression') {
      names.push(path.node.id.name);  // 箭头函数
    }
  }
});

增量更新的智能处理

// 只重新解析变化的部分,极大提升性能
let previousAST = null;

function onFileChange(newCode) {
  const newAST = incrementalParser.parse(newCode, previousAST);
  const issues = incrementalLint.check(newAST, changedRanges);
  previousAST = newAST; // 缓存供下次使用
}

复盘:AST是前端编译生态的"翻译官"

  1. 人类偏爱线性文本(易读、易diff)
  2. 机器偏爱树形结构(易遍历、易转换)
  3. AST充当中间表示,让两端高效对话
  4. 统一数据结构 → 工具链复用,避免重复造轮子

AST不是前端专属概念,但却是前端技术多样性最需要的"标准集装箱"。理解AST,就等于掌握了现代前端工程化的核心原理。

结语

文本是诗歌,树是骨架,AST就是那位沉默的翻译官——让人类的创意与机器的逻辑得以无障碍交谈。