Vue设计与实现:运行时与编译时
当设计一个框架的时候,我们有三种选择:纯运行时的、运行时 + 编译时的或纯编译时的。这需要你根据目标框架的特征,以及对框架的期望,做出合适的决策。
纯运行时框架
我们先聊聊纯运行时的框架。假设我们设计了一个框架,它提供一个 Render 函数,用户可以为该函数提供一个树型结构的数据对象,然后 Render 函数会根据该对象递归地将数据渲染成 DOM 元素。
数据结构定义
我们规定树型结构的数据对象如下:
const obj = {
tag: "div",
children: [{ tag: "span", children: "text" }],
};接下来我们来实现 Render 函数:
const Render = (obj, root) => {
const el = document.createElement(obj.tag);
if (typeof el.children === "string") {
const text = document.createTextNode(el.children);
el.appendChild(text);
} else if (el.children) {
obj.children.forEach((child) => Render(child, el));
}
root.appendChild(el);
};接下来我们结合一下:
const obj = {
tag: "div",
children: [{ tag: "span", children: "text" }],
};
const Render = (obj, root) => {
const el = document.createElement(obj.tag);
if (typeof el.children === "string") {
const text = document.createTextNode(el.children);
el.appendChild(text);
} else if (el.children) {
obj.children.forEach((child) => Render(child, el));
}
root.appendChild(el);
};
Render(obj, document.body);在浏览器中运行上面这段代码,就可以看到我们预期的内容。这就是纯运行时的框架。
运行时 + 编译时框架
直到有一天,有一位小伙伴提出:“手写树型结构的数据对象太麻烦了,而且不直观,能不能支持用类似于 HTML 标签的方式描述树型结构的数据对象呢?”
这时候就会开始思考,我能不能引入编译的手段,把 HTML 自动编译成树形结构的对象。
Compiler 程序的引入
为此你编写了一个叫做 Compiler 的程序。它的作用就是编译 HTML 字符串。最简单的使用方式就是让用户分别去调用 Compiler 和 Render:
const html = `<div>text</div>`;
const obj = Compiler(html);
Render(obj, document.body);上面这段代码能够很好地工作,这时我们的框架就变成了一个运行时 + 编译时的框架。它既支持运行 时,用户可以直接提供数据对象从而无须编译;又支持编译时,用户可以提供 HTML 字符串,我们将其 编译为数据对象后再交给运行时处理。准确地说,上面的代码其实是运行时编译,意思是代码运行的时候 才开始编译,而这会产生一定的性能开销,因此我们也可以在构建的时候就执行 Compiler 程序将用户 提供的内容编译好,等到运行时就无须编译了,这对性能是非常友好的。
纯编译时框架
不过,聪明的你一定意识到了另外一个问题:既然编译器可以把 HTML 字符串编译成数据对象,那么能不能直接编译成命令式代码呢?
直接编译为命令式代码
// HTML 模板
<div>
<span>text</span>
</div>
// 编译成命令式代码
const div = document.createElement('div')
const span = document.createElement('span')
span.innerText = 'text'
div.appendChild(span)
document.body.appendChild(div)这样我们只需要一个 Compiler 函数就可以了,连 Render 都不需要了。其实这就变成了一个纯编译时的框架,因为我们不支持任何运行时内容,用户的代码通过编译器编译后才能运行。
三种框架设计的对比
通过以上分析,我们可以总结出三种框架设计方案的特点和差异:
| 框架类型 | 工作方式 | 性能表现 | 灵活性 | 包体积 | 典型代表 |
|---|---|---|---|---|---|
| 纯运行时 | 运行时解析和渲染 | 较低 | 很高 | 较大 | 早期jQuery |
| 运行时 + 编译时 | 编译时优化 + 运行时处理 | 中等 | 高 | 中等 | Vue.js, React |
| 纯编译时 | 编译时完全处理 | 很高 | 较低 | 很小 | Svelte |