理解前端框架中的运行时和编译时

本文主要阐述前端框架中的运行时和编译时的区别。首先说明本文内容参考书籍 《Vue.js 设计与实现》，并加以自己的理解创作。有错误的地方还请指出。

运行时

运行时就是指代码实际执行时的阶段。前端代码是在浏览器中执行的，换言之，如果一个框架的代码可以直接在浏览器中执行，那它就是一个纯运行时的框架。

举个例子，假设我们设计了一个框架，它提供一个 Render 函数，用户使用时，为该函数提供一个描述 DOM 树形结构的参数对象，然后 Render 函数根据该对象递归地将数据渲染成 DOM 元素。假设规定要传入的对象结构如下：

Copy
const obj = {
tag: "div",
children: [
{
tag: "span",
children: "hello world!",
},
],
};

对象中有两个属性：tag 代表标签名称，children 可以是数组（代表子节点）也可以是文本（代表文本子节点）。然后实现 Render 函数：

Copy
function Render(obj, root) {
const el = document.createElement(obj.tag);
if (typeof obj.children === "string") {
const text = document.createTextNode(obj.children);
el.appendChild(text);
} else if (obj.children) {
obj.children.forEach((child) => Render(child, el));
}

root.appendChild(el);
}

现在可以在浏览器环境中调用 Render 函数：

Copy
// 渲染到 body 下
Render(obj, document.body);

此时就会看到预期的结果。这就是一个纯运行时的框架例子，因为浏览器可以直接运行 JavaScript 文件，所以它不需要任何的转换操作。

编译时

了解了运行时，那编译时就很好理解，它是指将源代码转换成可执行代码的阶段。在前端中，最简单的例子就是将高级的 ES6 语法转换为浏览器可以理解和执行的 ES5 语法，这个过程也是编译。

还是拿上面的代码示例来说明，如何将它改造成编译时的框架？但是首先我们需要知道，不是为了有编译时而加编译时，而是为了解决问题才有的编译时。

我们来看上面运行时的代码中有什么问题？很显然就是定义 DOM 树形结构对象有点麻烦，如果 DOM 结构很复杂，需要定义的对象就会嵌套很深，抽象不直观。所以我们在想可以直接像写 HTML 一样来声明 UI 吗？对，就是 vue 中模板（template）那样编写。首先我们应该知道，vue 文件中的 template 部分并不是 html，虽然长得很像，但是浏览器是不能直接识别它们的，所以 template 模板是需要编译的。那么编译成什么呢？

编译的结果是为了让浏览器能直接运行，那么假设我们的模板这样写：

Copy

hello world!


记住它不是 html 文件，所以我们需要将它编译成命令式代码：

Copy
const div = document.createElement("div");
const span = document.createElement("span");
span.innerText = "hello world!";
div.appendChild(span);
document.body.appendChild(div);

此时浏览器就可以直接执行代码并达到预期效果了。将这个过程封装成一个编译器，用户所有的代码只能通过编译器编译后才能执行，那它就是一个纯编译时框架，代表框架如 Svelte。

运行时 + 编译时

上面提到 vue 中的模板需要编译才能被浏览器执行，那 vue 是一个编译时框架吗？答案是否的。

vue 是一个运行时 + 编译时的框架。这个在官方文档中有提及运行时 vs 编译时响应性，文档中说到：

Vue 的响应式系统基本是基于运行时的。追踪和触发都是在浏览器中运行时进行的。

这里先不管 vue 中的实现细节。我们还是拿上面的例子说明，如果它是运行时 + 编译时，应该怎么实现？

其实就是加一个编译器就行，但不是直接编译成命令式创建代码，而是将模板编译成一个 DOM 描述对象，然后将对象传入渲染器。就是这样：

Copy

hello world!


编译成 ⬇️

Copy
const obj = {
tag: "div",
children: [
{
tag: "span",
children: "hello world!",
},
],
};

然后渲染：

Copy
Render(obj, document.body);

这就是一个运行时 + 编译时的过程，准确的说是一个运行时编译，就是代码执行时才编译。在实际框架使用中，我们可以在构建过程中执行编译，等真正运行时就无需编译，这样性能更好。

代码写到这，我们可能会想这样的运行时编译是不是多此一举，直接编译彻底不是更省事吗？理论上纯编译时的性能确实会更好，不需要运行时参与，代码直接编译成可执行的 JavaScript 代码，但是这也散失了部分灵活性，用户的内容必须编译后才能使用。而纯运行时，由于没有编译过程，就没办法分析用户提供的内容，从而不能做对应的优化，一般编译器中我们可以实现语法分析，优化和转换，可以执行语法错误检查，模块打包和代码压缩等任务。只能说框架作者在设计框架时，都是有自己的取舍，不能直接判断哪种更好。

使用框架 or 直接写 html

看到上面分析的一堆，我们不禁会想抛弃框架直接写 html 不是更直观省事。但事实我们也感受到了，使用 vue 或 react 这样的现代框架，能够大大增加我们的开发效率。原因就在于这些框架帮我们封装好了做事的过程，如果是传统原生 html 开发，我们需要维护实现目标的整个过程，包括要手动完成 DOM 元素的创建、更新、删除等工作。但框架让我们只需要为结果声明 UI，不需要关心实现过程。这就是框架的便利之处，了解运行时和编译时也是为了更好的了解框架的实现原理。

结尾

最后对运行时和编译时做一个总结。

运行时（Runtime）是代码实际执行时的阶段。在前端框架中，运行时通常指在浏览器环境中加载和执行已经编译好的代码。这个阶段包括了解析 HTML 结构、构建 DOM 树、执行 JavaScript 代码以及处理用户交互等任务。在运行时，前端框架会利用编译时生成的代码来动态地更新和渲染页面，处理事件和数据的变化等。

编译时（Compile Time）是指在开发过程中，将开发者编写的源代码转换为可执行的代码的阶段。例如 es6 转 es5，vue 模板转成 html 等。在编译时，前端框架的编译器会对代码进行语法分析、优化和转换，以生成可在浏览器中运行的代码。编译时的任务包括语法检查、模块打包、代码压缩等。