前言
很多朋友在看到 Vue3静态提升
的时候很不理解,不明白这句话到底是什么意思,今天我们就通过这篇日记来搞明白。如果有什么地方描述不正确,请多多指正。
静态类型(前置信息)
判断节点是否为静态类型,是根据如下标识进行判断的,可以对照这个进行查看。
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 // 一个特殊的标志,指代差异算法
}
Vue3基础示例
首先我们来看下面的这段代码,对于看过Vue的童鞋来讲是不陌生的吧,即使没有看过,看到如下的代码是不是觉得也很好理解。
<script setup>
import { ref } from 'vue';
const message = ref('努力搬砖,做大做强');
</script>
<template>
<!-- 这是静态节点 -->
<span>你好啊,帕努</span>
<!-- 这是动态节点 -->
<span>{{message}}</span>
</template>
将上面的代码转换成 虚拟DOM 后的图示。
对于上面的代码,vue3在创建 vNode 的时候,对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用,这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
。
创建vNode
针对上面的示例代码,在进行创建 虚拟DOM
的时候,下面的代码分别是 vue2
和 vue3
版本下各自的vNode,从这就能够看出一些差别。
Vue2 没有做静态提升
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("span", null, "你好啊,帕努"), // 静态虚拟节点
_createVNode("span", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
Vue3 做静态提升
// 把静态节点提出到外部定义,用一个变量存储,便于后面直接取值使用
const SPAN_V_NODE = _createVNode("span", null, "你好啊,帕努");
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
SPAN_V_NODE, // 静态虚拟节点
_createVNode("span", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
将静态虚拟节点提到外层,便于直接使用(只创建一次),下次就直接获取值放到对应的位置即可。避免了每次执行 render
函数的时候,都要去再次执行一次 _createVNode
函数。
如果是比较简单的项目,在性能上看不大出来,如果是比较大型的项目,那就能够很明显的看到界面卡顿(渲染慢),严重的甚至可能会造成 CPU 的使用率急速上升等明显问题。
通过上面的示例可能也看不出多大的效果(具体有多大的提升),下面将通过一段简单代码就能看出他们有多大的差距。
另辟蹊径解释
如果对于上面的代码如果还不能理解的话,下面再通过另外一个示例来讲一下。
在一个循环中,获取一个计算结果,然后通过这个结果再去计算打印,然后就能够看到两者差别。
没做静态提升
// 静态值,只会返回固定的18,
function getAgeFn() {
console.log('被调用获取值')
return 18;
}
function render() {
console.log('开始时间', new Date().getTime()) // 开始时间 1709628094030
for (let i = 0; i < 10000; i++){
console.log('获取到的值',i, i+getAgeFn() )
}
console.log('结束时间', new Date().getTime()) // 结束时间 1709628096175
}
render()
总运行时长:
1709628096175 - 1709628094030 = 2145
;
做了静态提升
function getAgeFn() {
console.log('被调用获取值')
return 18;
}
function render() {
console.log('开始时间', new Date().getTime()) // 1709627967983
const age = getAgeFn();
for (let i = 0; i < 10000; i++){
console.log('获取到的值',i, i+age )
}
console.log('结束时间', new Date().getTime()) // 1709627968909
}
render()
总运行时长:
1709627968909 - 1709627967983= 926
;
通过上面两段代码运行时间差就能够明显的看出效果,没做静态提升运行时长是做了静态提升的 2.32倍
(四舍五入)。
预字符串化
另外提到了静态提升
,那么就不得不提一下 Vue3的 预字符串化
,那这是什么呢?有什么作用呢?
我们在实际开发中,经常会有大量的静态节点,是不需要动态更改的。那这一部分能不能再进行优化一下呢?答案是可行的。
<div>
<ul>
<li>aaaaaa</li>
<li>aaaaaa</li>
<li>aaaaaa</li>
<li>aaaaaa</li>
<li>aaaaaa</li>
<li>aaaaaa</li>
</ul>
<div class="state">
<span>{{state}}</span>
</div>
<div>
在vue2中,每个元素都会变成虚拟节点,这样就会出现一大堆的静态的虚拟节点。
在vue3中它会智能地发现这一点,如下图所示,我们可以很明显的感受到vue3性能的显著提升。
注意:必须是 大量连续 的静态内容才可以预字符串化哦,切记!目前是连续 20个静态节点 才会预字符串化
总结
静态提升
Vue 2 并没有像 Vue 3 中那样实现静态提升(Static Tree Hoisting)。在 Vue 2 中,模板编译后的渲染函数是动态创建的,每次渲染都需要重新生成一次。这导致了一些性能上的损失,特别是在大型应用中频繁重新渲染时。
Vue 3 引入了静态提升的概念,通过将模板编译为更具静态特性的代码,可以在编译阶段优化并提升静态节点,减少运行时的开销,从而提高性能。这是 Vue 3 在性能方面的一个重要改进之一。
预字符串化
在编译阶段会对模板进行分析和优化,将动态部分和静态部分分离,并将静态部分转换为字符串常量,以减少运行时的开销。
预字符串化可以帮助 Vue 3 在渲染过程中更快地生成虚拟 DOM,并减少不必要的计算,从而提高整体的渲染性能。这个优化技术有助于减少应用的启动时间和运行时的性能消耗,使得 Vue 3 在处理大型复杂应用时表现更加出色。
这些骚操作总结一句话就是【提高性能】
最后
如果这篇文章对你有所帮助,请咚咚大家的 发财黄金手指
,点赞,收藏。
如果文章有描述错误或者有更好解决方案,也请大家多多 评论。