人云亦云,并不会让你变得有多优秀,而会让你越来越随大流。
当你和别的开发在聊到 Vue 3.0 版本发布,有哪些亮点时,你的答案之一肯定有“它变得更快了,性能上快了 1.2 ~ 2倍”。
那么我就想问你,是什么让 Vue 变快了,尤大已经在 beta 版的线上直播上告诉了我们答案。
PatchFlag(静态标记)
Vue 2.x 中的虚拟 DOM 是全量对比的模式,而到了 Vue 3.0 开始,新增了静态标记(PatchFlag)。
在更新前的节点进行对比的时候,只会去对比带有静态标记的节点。并且 PatchFlag 枚举定义了十几种类型,用以更精确的定位需要对比节点的类型。下面我们通过图文实例分析这个对比的过程。
假设我们有下面一段代码:
<div>
<p>老八食堂</p>
<p>{{ message }}</p>
</div>
在 Vue 2.x 的全量对比模式下,如下图所示:
通过上图,我们发现,Vue 2.x 的 diff 算法将每个标签都比较了一次,最后发现带有 {{ message }}
变量的标签是需要被更新的标签,显然这还有优化的空间。
在 Vue 3.0 中,对 diff 算法进行了优化,在创建虚拟 DOM 时,根据 DOM 内容是否会发生变化,而给予相对应类型的静态标记(PatchFlag),如下图所示:
观察上图,不难发现视图的更新只对带有 flag 标记的标签进行了对比(diff),所以只进行了 1 次比较,而相同情况下,Vue 2.x 则进行了 3 次比较。这便是 Vue 3.0 比 Vue2.x 性能好的第一个原因。
我们再通过把模板代码转译成虚拟 DOM,来验证我们上述的分析是否正确。我们可以打开模板转化网站,对上述代码进行转译:
上图蓝色框内为转译后的虚拟 DOM 节点,第一个 P 标签为写死的静态文字,而第二个 P 标签则为绑定的变量,所以打上了 1 标签,代表的是 TEXT(文字),标记枚举类型如下:
export const enum PatchFlags {
TEXT = 1,// 动态的文本节点
CLASS = 1 << 1, // 2 动态的 class
STYLE = 1 << 2, // 4 动态的 style
PROPS = 1 << 3, // 8 动态属性,不包括类名和样式
FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 动态 solt
HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff
BAIL = -2 // 一个特殊的标志,指代差异算法
}
hoistStatic(静态提升)
我们平时在开发过程中写函数的时候,定义一些写死的变量时,都会将变量提升出去定义,如下所示:
const PAGE_SIZE = 10
function getData () {
$.get('/data', {
data: {
page: PAGE_SIZE
},
...
})
}
诸如上述代码,如果将 PAGE_SIZE = 10
写在 getData
方法内,每次调用 getData
都会重新定义一次变量。
Vue 3.0 在这方面也做了同样的优化,继续用我们上一个例子写的代码,观察编译之后的虚拟 DOM 结构,如下所示:
没有做静态提升前:
选择 Option 下的 hoistStatic
:
静态提升后:
细心的同学会发现, 老八食堂
被提到了 render
函数外,每次渲染的时候只要取 _hoisted_1
变量便可。认真看文章的同学又会发现一个细节, _hoisted_1
被打上了 PatchFlag
,静态标记值为 -1 ,特殊标志是负整数表示永远不会用作 Diff。也就是说被打上 -1 标记的,将不在参与 Diff 算法,这又提升了 Vue 的性能。
cacheHandler(事件监听缓存)
默认情况下 @click
事件被认为是动态变量,所以每次更新视图的时候都会追踪它的变化。但是正常情况下,我们的 @click
事件在视图渲染前和渲染后,都是同一个事件,基本上不需要去追踪它的变化,所以 Vue 3.0 对此作出了相应的优化叫事件监听缓存,我们在上述代码中加一段:
<div>
<p @click="handleClick">屋里一giao</p>
</div>
编译后如下图所示(还未开启 cacheHandler):
在未开启事件监听缓存的情况下,我们看到这串代码编译后被静态标记为 8,之前讲解过被静态标记的标签就会被拉去做比较,而静态标记 8 对应的是“动态属性,不包括类名和样式”。 @click
被认为是动态属性,所以我们需要开启 Options 下的 cacheHandler
属性,如下图所示:
细心的同学又会发现,开启 cacheHandler
之后,编译后的代码已经没有静态标记(PatchFlag),也就表明图中 P 标签不再被追踪比较变化,进而提升了 Vue 的性能。
SSR 服务端渲染
当你在开发中使用 SSR 开发时,Vue 3.0 会将静态标签直接转化为文本,相比 React 先将 jsx 转化为虚拟 DOM,再将虚拟 DOM 转化为 HTML,Vue 3.0 已经赢了。
StaticNode(静态节点)
上述 SSR 服务端渲染,会将静态标签直接转化为文本。在客户端渲染的时候,只要标签嵌套得足够多,编译时也会将其转化为 HTML 字符串,如下图所示:
需要开启 Options 下的 hoistStatic
总结
以上便是 Vue3.0 在编译时针对虚拟 DOM 的性能优化,这使得 Vue 3.0 在性能上是 Vue 2.x 的 1.2~2倍。
创建了一个 Vue3 的学习仓库 vue3-examples,仓库地址:https://github.com/newbee-ltd/vue3-examples,此仓库将不定期更新各种 Vue3.0 相关的知识及各种整合 Demo 及 Vue3 使用小技巧,大家可以关注一下,有什么建议也欢迎大家给我留言。
除注明转载/出处外,皆为作者原创,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。