【vue篇】Vue 模板编译全解析:从 Template 到 DOM 的奇妙旅程

当你写下:

vue 复制代码
<template>
  <div class="title">{{ message }}</div>
</template>

Vue 内部究竟发生了什么?

"模板是如何变成可执行的 JavaScript 函数的?" "为什么 {{ message }} 能自动更新?" "静态内容是如何被优化的?"

本文将带你深入 Vue 编译器核心,揭秘 Template → AST → Render 函数 的完整链路。


一、核心流程:三步编译流水线

text 复制代码
Template (字符串)
     ↓
   parse() → AST (抽象语法树)
     ↓
optimize() → 优化 AST(标记静态节点)
     ↓
generate() → Render 字符串
     ↓
new Function() → Render 函数
     ↓
执行 → VNode → DOM

二、第一步:parse → 生成 AST

✅ 目标

将模板字符串转换为 抽象语法树(AST),用 JavaScript 对象描述模板结构。

📌 示例模板

html 复制代码
<div class="title" v-if="show">
  Hello {{ name }}
  <span>Static Text</span>
</div>

🌲 生成的 AST 结构

js 复制代码
{
  type: 1, // 元素节点
  tag: 'div',
  attrsList: [
    { name: 'class', value: 'title' },
    { name: 'v-if', value: 'show' }
  ],
  children: [
    {
      type: 3, // 文本节点
      text: 'Hello ',
      static: false
    },
    {
      type: 2, // 表达式节点
      expression: '_s(name)',
      text: '{{ name }}',
      static: false
    },
    {
      type: 1,
      tag: 'span',
      children: [
        { type: 3, text: 'Static Text', static: true }
      ],
      static: true // 整个 span 是静态的
    }
  ],
  static: false,
  staticRoot: false
}

🔧 parse 如何工作?

  1. 正则匹配:顺序扫描模板字符串;
  2. 识别标记
    • <div> → 开始标签 → 执行 start() 回调;
    • </div> → 结束标签 → 执行 end() 回调;
    • Hello {{ name }} → 文本 → 执行 chars() 回调;
  3. 构建树结构:通过栈结构维护父子关系。

💡 类似 HTML 解析器,但输出是 AST 而非 DOM。


三、第二步:optimize → 静态节点优化

✅ 目标

标记永不变化的节点,在后续更新中跳过 diff,极大提升性能。

🎯 优化策略

节点类型 是否静态 判断依据
<span>Static</span> 纯文本,无绑定
{{ message }} 包含变量
<div v-if="false"> 指令依赖响应式数据
<img src="./logo.png"> 属性无绑定

📊 优化后 AST 新增标记

js 复制代码
{
  tag: 'span',
  static: true,      // 标记为静态节点
  staticRoot: true,  // 标记为静态根(可提升)
  children: [/* ... */]
}

⚡ 优化带来的性能收益

  • 首次渲染:静态节点生成的 VNode 可被缓存;
  • 更新渲染 :直接复用缓存的 VNode,跳过 patch
  • 内存节省:减少不必要的 VNode 创建。

四、第三步:generate → 生成 Render 函数代码

✅ 目标

将优化后的 AST 转换为可执行的 render 函数字符串。

🧩 生成的 Render 字符串

js 复制代码
with(this) {
  return (show) 
    ? _c('div', { staticClass: "title" }, [
        _v("Hello " + _s(name)),
        _c('span', [_v("Static Text")])
      ])
    : _e()
}

🔍 关键函数解析

函数 作用 源码对应
_c createElement 创建 VNode
_v createTextVNode 创建文本节点
_s toString 转换表达式
_e createEmptyVNode 创建空节点

🏗️ generate 如何工作?

  1. 深度优先遍历 AST
  2. 根据节点类型生成对应代码
    • 元素节点 → _c(tag, data, children)
    • 文本节点 → _v(text)
    • 表达式 → _s(expression)
  3. 处理指令
    • v-if → 三元表达式;
    • v-for_l(array, function)
    • v-on → 事件监听对象

五、最终:创建 Render 函数

js 复制代码
const render = new Function(`with(this){return ${code}}`)

render 函数执行结果

js 复制代码
// 执行 render() 返回 VNode
{
  tag: 'div',
  data: { staticClass: 'title' },
  children: [
    { text: 'Hello Vue', ... },
    { tag: 'span', children: [...], ... }
  ]
}

六、Vue 3 的编译优化

🚀 编译时优化

优化 说明
静态提升 将静态节点提升到 render 外,只创建一次
Patch Flag 动态节点打标记,diff 时只比对相关部分
Tree Shaking 未使用的转换函数被摇除

📌 Vue 3 Render 函数示例

js 复制代码
import { createElementVNode as _createElementVNode } from 'vue'

const _hoisted_1 = /*#__PURE__*/ _createElementVNode("span", null, "Static Text", -1 /* HOISTED */)

return function render(_ctx, _cache) {
  return (_ctx.show)
    ? (_openBlock(), _createElementVNode("div", { class: "title" }, [
        _createTextVNode("Hello " + _toDisplayString(_ctx.name), 1 /* TEXT */),
        _hoisted_1
      ], 64 /* STABLE_FRAGMENT */))
    : _createEmptyVNode()
}

💥 -1 表示该节点永不更新1 表示只 diff 文本内容


七、开发模式 vs 生产模式

模式 编译时机 调试支持
开发模式 运行时编译(vue.js) 模板错误定位精确
生产模式 构建时编译(vue.runtime.js) 体积小、性能高

✅ 推荐:使用 vue-cli / Vite 在构建时编译模板


💡 结语

"Vue 模板不是魔法,而是编译的艺术。"

阶段 输入 输出 目的
parse 模板字符串 AST 结构化描述
optimize AST 优化 AST 标记静态内容
generate 优化 AST Render 字符串 生成可执行代码

掌握模板编译过程,你就能:

✅ 理解 {{ }} 和指令的底层原理;

✅ 优化模板结构提升性能;

✅ 调试编译错误更高效;

✅ 为学习 Vue 3 编译优化打下基础。

相关推荐
浏览器工程师1 天前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆1 天前
VSCode自动格式化三要素
前端
爱勇宝1 天前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen1 天前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518131 天前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode1 天前
Redis 在生产项目的使用
前端·后端
LiaCode1 天前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战1 天前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
风骏时光牛马1 天前
# Ruby基于Rails框架实现多角色权限管理与数据分页查询完整实战代码案例
前端
weedsfly1 天前
迭代器、生成器与异步迭代——让数据“按需流动”的艺术
前端·javascript