如前所述,组件的实现依赖于渲染器,模板的编译依赖于编译器,并且编译后生成的代码是根据渲染器和虚拟 DOM 的设计决定的, 因此 Vue.js 的各个模块之间是互相关联、互相制约的,共同构成一个有机整体。因此,我们在学习 Vue.js 原理的时候, 应该把各个模块结合到一起去看,才能明白到底是怎么回事。
这里我们以编译器和渲染器这两个非常关键的模块为例,看看它们是如何配合工作,并实现性能提升的。
假设我们有如下模板:
<div id="foo" :class="cls"></div>根据上文的介绍,我们知道编译器会把这段代码编译成渲染函数:
render() {
// 为了效果更直观,这里没有使用 h 函数,而是直接采用了虚拟 DOM 对象
// 下面的代码等价于
// return h('div', { id: 'foo', class: cls })
return {
tag: 'div',
props: {
id: 'foo',
class: cls
}
}
}可以发现,在这段代码中,cls 是一个变量,它可能会发生变化。我们知道渲染器的作用之一就是寻找并且只更新变化的内容, 所以当变量 cls 的值发生变化时,渲染器会自行寻找变更点。对于渲染器来说,这个“寻找”的过程需要花费一些力气。
那么从编译器的视角来看,它能否知道哪些内容会发生变化呢?如果编译器有能力分析动态内容,并在编译阶段把这些信息提取出来, 然后直接交给渲染器,这样渲染器不就不需要花费大力气去寻找变更点了吗?这是个好想法并且能够实现。
Vue.js 的模板是有特点的,拿上面的模板来说,我们一眼就能看出其中 id="foo" 是永远不会变化的,而 :class="cls" 是一个 v-bind 绑定, 它是可能发生变化的。所以编译器能识别出哪些是静态属性,哪些是动态属性,在生成代码的时候完全可以附带这些信息:
render() {
return {
tag: 'div',
props: {
id: 'foo',
class: cls,
},
patchFlags: 1. // 假设数字 1 表示这个 class 是动态的
}
}如上面的代码所示,在生成的虚拟 DOM 对象中多出了一个 patchFlags 属性,我们假设数字 1 代表“ class 是动态的”, 这样渲染器看到这个标志时就知道:“哦,原来只有 class 属性会发生改变。”对于渲染器来说,就相当于省去了寻找变更点的工作量,性能自然就提升了。
通过这个例子,我们了解到编译器和渲染器之间是存在信息交流的,它们互相配合使得性能进一步提升,而它们之间交流的媒介就是虚拟 DOM 对象。 在后面的学习中,我们会看到一个虚拟 DOM 对象中会包含多种数据字段,每个字段都代表一定的含义。
