深入浅出 Vue 核心设计哲学:为什么要划分编译时与运行时?只留一个行不行?

9 阅读6分钟

前言

在前端面试或日常进阶阅读中,我们经常会看到 Vue 的构建版本里有 vue.runtime.jsvue.js(包含编译器)的区别。你有没有想过:Vue 为什么要费尽心机地把框架拆成“编译时(Compiler)”和“运行时(Runtime)”两个部分?如果像 Svelte 那样只留编译时,或者像早期 jQuery 插件那样只留运行时,难道不行吗?

今天这篇文章,我们就来彻底扯透 Vue 的这层设计哲学。

一、 快速破题:只保留一个行不行?

答案是:行,完全可以。

如果我们把目光投向现有的前端框架生态,就会发现这两种极端都有人走:

  • 只留编译时: 代表作是 Svelte。它没有虚拟 DOM,在打包时直接把组件编译成极其精简的纯原生 DOM 操作。
  • 只留运行时: 比如早期的 jQuery 时代,或者你在 Vue 中完全不写 <template> ,全部手写 h() 渲染函数。

既然单走一条路也能通,为什么 Vue 偏偏要选择“既要编译时,又要运行时”的双剑合璧模式?因为它要在开发体验、运行性能、文件体积之间找到那个完美的“黄金平衡点”。

二、 编译时 vs 运行时:它们在 Vue 里各司其职?

我们可以用一个通俗的通俗类比来理解这两个阶段的分工:

[.vue 文件] ───(编译时 Compiler)───> [Render 函数] ───(运行时 Runtime)───> [真实 DOM]
 (看不懂的模版)        (设计师/翻译官)         (纯 JS 虚拟 DOM)        (施工队)

1. 编译时(Compiler):设计师兼翻译官

你在 .vue 文件里写的 <template> 模版,浏览器是根本不认识的。 Compiler 的工作发生在打包构建阶段(Node.js 环境) 。它负责把模版“翻译”成浏览器能看懂的、高效的 JavaScript 渲染函数(Render Functions)。

2. 运行时(Runtime):硬核施工队

Runtime 的工作发生在浏览器运行阶段。当用户打开网页时,Runtime 负责创建虚拟 DOM、监听数据变化(响应式系统)、执行新旧虚拟 DOM 的对比(Diff 算法),并真正地去操作 DOM,把画面渲染出来。

三、 划分边界带来的“三大红利”

Vue 之所以坚持这种划分,是因为它能带来单一路线无法企及的优势:

1. 极致的体积优化(Tree-shaking)

这是划分边界带来的最直接好处。

  • 线上不需要编译器: 用户在浏览器里访问你的网站时,只需要执行“施工”(Runtime)的代码,根本不需要“翻译”(Compiler)的代码。
  • 体积暴减: Vue 的编译器代码大约占了整体体积的 30% 左右。通过工程化工具(如 Vite/Webpack)在打包时把编译器剥离,用户下载的 JS 文件变小了,首屏加载速度自然更快。

2. 编译时留线索,运行时享红利(AOT 优化)

如果框架纯靠运行时去解析模版,它就是“盲目”的。运行时看到一堆 DOM 节点,根本不知道哪些是动态数据,哪些是永远不会变的静态文本,只能傻傻地全量去对比 Diff。

而 Vue 因为有了编译时,可以在打包时对模版进行静态分析(Static Analysis)

  • 编译器在编译时发现某个 <div> 是纯静态的,就会打上标记,告诉运行时:“这段话是死数据,动态更新时直接跳过它”。
  • 编译器发现某个 <span :id="val"> 只有 id 是动态的,就会生成带有 Patch Flags(靶向更新标记) 的代码,告诉运行时:“你只需要盯着它的 id 属性看,内容不用管”。

这种“编译时留线索,运行时享红利”的 AOT(预编译)优化,是 Vue 3 性能飞跃的核心秘密。

3. 天然跨端,包容多端生态

有了独立的运行时,Vue 就不再与浏览器(Web DOM)强绑定。

  • 只要你编写一个不同的编译器(比如把模版编译成小程序语法),或者定制一个不同的运行时(比如把虚拟 DOM 映射成 iOS/Android 的原生应用组件)。
  • 这就是 Uni-app、Weex 等跨端框架能够基于 Vue 繁荣发展的根本原因。

四、 假如“只保留一个”,代价是什么?

为了让你更深刻地理解,我们不妨做两个极端的反向假设:

假设 A:砍掉编译时,只留运行时(Runtime-Only)

意味着没有 <template> 模版了,你不能写任何类似于 HTML 的代码。

// 告别 template,你必须刻苦地手写纯 JS 渲染函数:
export default {
  render() {
    return h('div', { class: 'container' }, [
      h('h1', this.title),
      h('p', this.content)
    ])
  }
}
  • 惨痛代价: 开发体验倒退回石器时代。没有直观的结构,嵌套稍微深一点的组件会让人写到抓狂,且由于失去了静态分析,运行时的 Diff 算法只能做全量对比,性能大打折扣。

假设 B:砍掉运行时,只留编译时(Compiler-Only)

就像 Svelte 那样,把所有的逻辑、数据绑定、DOM 更新全部在打包时生成好纯原生 JS 代码。

  • 惨痛代价: 失去了解析动态模版的能力。如果你从后端接口请求了一段 HTML 模版,想在运行时动态渲染出来(类似于 Vue 的 v-html 或动态组件 index.vue 挂载),没有运行时的框架会非常难处理。此外,当项目规模极度庞大、组件成百上千时,每个组件编译出来的原生操作 DOM 的代码量,可能会超过通用运行时的体积(边际效应)。

五、 总结:Vue 的中庸与优雅

Vue 团队在设计上选择了架构分层的智慧。它通过划分编译时和运行时,让我们在写代码时享受 <template> 的直观与舒适(编译时关怀),在线上运行时享受极致的体积与靶向更新的性能(运行时红利)。

在实际开发中,Vue 为我们提供了两套构建版本:

构建版本包含内容体积表现适用场景
Runtime + Compiler模版解析器 + 虚拟 DOM + 响应式完整体积在浏览器中直接用 <script> 标签引入 Vue,且需要在 HTML 里直接写模版。
Runtime-Only <-- 推荐虚拟 DOM + 响应式缩减约 30%使用 Vite/Webpack 等工程化工具,.vue 在本地已编译完成,浏览器只负责纯粹运行。

明白了这个道理,下次面试再被问到“Vue的打包优化”或者“Vue3性能提升在哪里”,你就可以从容地从“编译时与运行时的协同优化”这个底层逻辑切入,给面试官一个小小的震撼了!

觉得有收获的话,欢迎点赞、收藏、关注三连!你在实际开发中更倾向于模板语法还是 JSX 呢?欢迎在评论区留下你的看法!