【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 编译优化打下基础。

相关推荐
weixin_382395237 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__7 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.8 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~9 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华9 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子12 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅12 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni12 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学13 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端