Vdom (虚拟dom)凭借着出色的性能成为了目前的主流的前端框架都会选择的渲染方案。再加上优秀的 diff 算法对它的一步步的优化,使框架的价值得到了极致的体现,几乎成为了我们前端开发必不可少的方案。
我们已经知道,Vue2.x 中的 Vdom 已经相当出色了,性能非常优秀。不过令人兴奋的是,尽管它够快,但在 Vue3 中还是对 Vdom 进行了重写,使 Vue3 突破了 Vdom 的性能瓶颈,更快!
Vue3 如何重写 Vdom
在开始介绍之前,先给大家推荐一个网站:[https://vue-next-template-explorer.netlify.app]
这是 Vue3 官方的演示 Vdom 的示例网站,本篇文档也是基于它进行演示。
1. 入门
当我们创建一个这样的静态 dom 元素的时候:
<span>Hello World</span>
Vue3 给我们编译后的 Vdom 是这个样子的:
看似比较复杂,实际上 _createBlock 函数中才是我们创建的 dom,从它身上我们可以看出,我们创建了一个 span 元素,内容为 “Hello World!”。这就是 Vdom 最基础的形式,在这里我们并不会感觉到 Vue3 与 Vue2 有什么不同。
2. patch flag 优化静态树
当我们创建了一个动态的 dom 元素:
![](/qrcode.jpg)
<span>Hello World</span>
<span>Hello World</span>
<span>Hello World</span>
<span>Hello World</span>
<span>{ {msg}}</span>
<span>Hello World</span>
<span>Hello World</span>
Vue3 编译后的 Vdom
是这个样子的:
我们发现创建动态 dom 元素的时候,Vdom 除了模拟出来了它的基本信息之外,还给它加了一个标记: 1 /* TEXT */
这个标记就叫做 patch flag(补丁标记)
patch flag 的强大之处在于,当你的 diff 算法走到 _createBlock 函数的时候,会忽略所有的静态节点,只对有标记的动态节点进行对比,而且在多层的嵌套下依然有效。
尽管 JavaScript 做 Vdom 的对比已经非常的快,但是 patch flag 的出现还是让 Vue3 的 Vdom 的性能得到了很大的提升,尤其是在针对大组件的时候。
3 . patch flag 优化静态属性
1. 静态绑定
-
当我们创建一个有属性的元素
<span id="hello">{ {msg}}</span>
- Vue3 编译后的
Vdom
是这个样子的:
让我们观察它的 patch flag
,发现并没有对 id 做特殊的标记。是因为 dom 元素的静态属性在渲染的时候就已经创建了,并且是不会变动的,在后面进行更新的时候,diff 算法是不会去管它的。
2. 动态绑定
-
当我们创建一个属性是动态绑定的元素:
<span :id="hello">{ {msg}}</span>
- Vue3 编译后的
Vdom
是这个样子的:
再观察它的 patch flag ,会发现变成了 9 /* TEXT, PROPS */,而且后边多了一个数组 ["id"]
这里的 patch flag 中的注释的内容告诉我们,这一个 dom 元素不止有内容 TEXT 会变化,它的属性 PROPS 也会变化。而后边的数组中的内容则是有可能发生变化的属性。
看到这里,我们就会明白 Vue3 实际做的事情了。
Vue3 在 Vdom 的更新时,只会关注它有变化的部分。这样的优化使 Vue3 既跳出了 Vdom 的性能瓶颈,又依然保留了可以手写 render function 的灵活性。相当于 Vue3 既有 react 的灵活性,又有基于模板的性能保证。——尤雨溪
3. 静态提升
刚刚我们提到 Vue3 突破 Vdom
的性能瓶颈的方式是,只关注它有变化的部分。而在更新时具体是怎么做的呢?
具体的做法就是 静态树的提升 和 静态属性的提升
-
我们创建若干的 dom 元素:
<span>Hello World</span>
<span>Hello World</span>
<span>Hello World</span>
<span>{ {msg}}</span>
<span>Hello World</span>
<span>Hello World</span>
<span>Hello World</span>
<span>Hello World</span>
<span>Hello World</span>
静态提升之后:
我们已经知道处理后的 Vdom
都在 _createBlock
函数之中,而观察结果我们发现,所有的静态元素都被放在了 _createBlock
函数之外了,也就是说他们只会在页面初始的时候被渲染一次,而在更新的时候,静态元素是不予搭理的。
这个优化就是 Vue3 的 静态提升
4. 事件侦听器缓存
了解 react 的同学都知道,在我们使用 react 时,对其性能优化的其中一点就是将侦听方法手动进行缓存,避免更新组件时被多次重新创建。而 Vue3 直接替我们做了这一点
-
创建一个带有事件侦听的元素
<div @click="hander">
<span>static</span>
</div>
没有进行事件侦听器缓存的 Vdom
在这里的 _createBlock
函数中就是这个元素的 Vdom
结构。观察高亮的地方,发现 onClick
函数以变量的形式存在。
-
进行事件侦听器缓存后的
Vdom
观察高亮的那一行,我们发现 onClick
函数的储存位置变成了缓存的形式。也就是说 当你的页面在不断的更新的时候,你的事件侦听器并不会重复地销毁再创建,而是以缓存的形式存在,这使 Vue3 在性能方面又有了一个出彩的地方。
另外,Vue3 在
@click
中,直接手写内联函数也会被缓存起来,这一点是 react 做不到的。再加上 在 Vue3 中父组件的更新并不会直接触发子组件的更新,使得事件侦听器缓存在组件的层面可以提现出来更高的价值。