原来dom树就是AST!!!

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

本文记录了我对AST从困惑到顿悟的认知之旅,从发现"DOM树就是AST"到理解AST如何成为前端世界的"标准集装箱",消除了语法差异,让机器更好地理解人类代码。

从一次代码调试说起

那天,我在调试一个Babel插件时,第一次真正接触到了AST(抽象语法树)。看着密密麻麻的节点结构,我产生了深深的困惑:

go 复制代码
// 我写的代码
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" }
    }
  }]
}

​为什么要把简单的代码转换成这么复杂的树结构?​​ 这个疑问开启了我的探索之旅。

突破认知:原来DOM树就是AST!

在深入理解后,我发现了第一个令人惊讶的事实:

我们每天都在用AST,只是不知道它叫这个名字

xml 复制代码
<!-- 我们熟悉的HTML -->
<div class="container">
  <h1>标题</h1>
  <p>段落内容</p>
</div>

这段HTML被浏览器解析后,会生成​​DOM树​ ​。而DOM树,本质上就是​​HTML的AST​​!

css 复制代码
// 这就是HTML的AST(DOM树)!
{
  type: 'document',
  children: [{
    type: 'element',
    tagName: 'div',
    attributes: [{ name: 'class', value: 'container' }],
    children: [{
      type: 'element',
      tagName: 'h1',
      children: [{ type: 'text', content: '标题' }]
    }, {
      type: 'element', 
      tagName: 'p',
      children: [{ type: 'text', content: '段落内容' }]
    }]
  }]
}

同样地,CSS也有自己的AST------​​CSSOM​​(CSS对象模型):

css 复制代码
/* CSS代码 */
.container { color: red; font-size: 16px; }

/* 对应的CSS AST(CSSOM) */
{
  type: 'stylesheet',
  rules: [{
    type: 'rule',
    selectors: ['.container'],
    declarations: [
      { property: 'color', value: 'red' },
      { property: 'font-size', value: '16px' }
    ]
  }]
}

AST的"超能力":消除语法差异的"标准集装箱"

在进一步探索中,我发现了AST最强大的地方:​​它像国际海运的标准集装箱一样,消除了各种语法"方言"的差异​​。

为什么前端需要"标准化集装箱"?

想象前端技术的多样性:

typescript 复制代码
// 各种语法"方言"
const element = <div className="title">Hello</div>;    // JSX
interface User { name: string; age: number; }          // TypeScript
const user = { ...defaults, ...overrides };            // ES2018+
const result = data?.user?.profile?.name;              // 可选链

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

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

​有AST的美好现实​​:

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

AST如何实现"一种结构,多种语法"

看看箭头函数如何被统一处理:

dart 复制代码
// 不同写法的箭头函数
const add = (a, b) => a + b;
const square = x => x * x;
const lazy = () => ({ result: 42 });

// 统一转换成AST后的核心结构
{
  type: "ArrowFunctionExpression",  // 关键节点类型!
  params: [{ type: "Identifier", name: "a" }, ...],
  body: { type: "BinaryExpression", operator: "+", ... },
  expression: true
}

// Babel在AST层面统一转换
const visitor = {
  ArrowFunctionExpression(path) {
    // 所有箭头函数都走这个处理流程
    path.replaceWith(convertToRegularFunction(path.node));
  }
};

三大AST的个性对比

通过对比,我发现了一个有趣的现象:

DOM树:前台的"社交达人"

  • ​特点​:从页面加载到关闭一直存在
  • ​曝光度高​ :通过document.getElementById等API直接操作
  • ​可视化​:浏览器开发者工具可以实时查看和调试

JS AST:幕后的"临时工"

  • ​特点​:生命周期极短,解析完就被编译成字节码
  • ​低调​:藏在V8引擎内部,开发者很少直接接触
  • ​高效​:完成使命就"功成身退"

CSSOM:专注的"设计师"

  • ​特点​:专注样式计算和层叠规则
  • ​专业​:处理颜色、布局、动画等视觉表现
  • ​精确​:确保样式按正确规则应用

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

到这里,我意识到了AST存在的​​根本原因​​:

人类的偏好:线性文本

我们喜欢一行行写代码,因为:

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

机器的需求:树形结构

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

  • ✅ 易于分析语法关系
  • ✅ 方便进行优化转换
  • ✅ 支持高效遍历查询
css 复制代码
// 机器友好的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的模板编译

xml 复制代码
<template>
  <div @click="handleClick" :class="{ active: isActive }">
    {{ message }}
    <ChildComponent :data="list" />
  </div>
</template>

<!-- Vue编译器的工作流程: -->
<!-- 1. 解析模板 → 生成模板AST -->
<!-- 2. 优化AST(标记静态节点) -->
<!-- 3. 生成渲染函数 -->

React的JSX转换

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

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

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

案例:自动化代码重构

团队从MobX切换到Redux,手动修改几百个文件不现实:

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

案例:自定义团队规范

要求所有console.log必须改为自定义日志:

javascript 复制代码
// 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的性能优势:为什么"多此一举"反而更快

你可能会想:直接操作文本不是更简单吗?为什么非要转成AST?

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

scss 复制代码
// 文本操作:用正则查找函数名(容易误匹配)
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);  // 箭头函数
    }
  }
});

增量更新的智能处理

现代工具利用AST实现智能缓存:

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

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

复盘

AST 绝非前端专属概念,更像是行业通用的 "结构化规范"------ 类似数据结构或设计模式,核心是介于文本与机器语言之间的结构化中间态。它的关键优势的是能被程序高效理解,将其作为 "可复用的数据结构" 运用,可让程序工具便捷调用与适配,大幅提升跨场景复用效率。

相关推荐
~无忧花开~4 小时前
掌握Axios:前端HTTP请求全攻略
开发语言·前端·学习·js
橙某人5 小时前
Vue3 + Pinia 移动端Web应用:页面缓存策略解决方案💡
前端·javascript·vue.js
小Pawn爷5 小时前
构建Django的Web镜像
前端·python·docker·django
Sailing5 小时前
🚀🚀 从前端到AI Agent开发者,只差这一篇入门指南
前端·后端·ai编程
草帽lufei5 小时前
轻松上手WSL安装与使用
linux·前端·操作系统
TimelessHaze5 小时前
🚀 一文吃透 React 性能优化三剑客:useCallback、useMemo 与 React.memo
前端·javascript·react.js
长存祈月心5 小时前
Rust 迭代器适配器
java·服务器·前端
先树立一个小目标6 小时前
puppeteer生成PDF实践
前端·javascript·pdf
冲刺逆向6 小时前
【js逆向案例二】瑞数6 深圳大学某医院
前端·javascript·vue.js