前言
在前端面试或日常进阶阅读中,我们经常会看到 Vue 的构建版本里有 vue.runtime.js 和 vue.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 呢?欢迎在评论区留下你的看法!