- 动态节点收集和补丁标记
- block 树
- 静态提升
- 预字符串化
- 缓存内联事件处理函数
- v-once
1. 动态节点收集和补丁标记
Vue3编译优化指的是通过编译的手段提取关键信息,并以此指导生成最优代码的过程。具体来说,Vuejs3的编译器会充分分模板,提取关键信息并将其附着到对应的虚拟节点上。在运行时阶段,渲染器通过这些关键信执行“快捷路径”,从而提升性能。
编译优化的核心在于,区分动态节点与静态节点。Vue.js3会为动态节点打上补丁标志, patchFlag。同时,Vue.js3还提出了 Block 的概念,一个 Block 本质上也是一个虚拟节点,但普通虚拟节点相比,会多出一个 dynamicChildren 数组。该数组用来收集所有动态子代节点,这利用了createVNode 函数和 createBlock 函数的层层嵌套调用的特点,即以“由内向外”的大执行。再配合一个用来临时存储动态节点的节点栈,即可完成动态子代节点的收集
<template>
<h1>{{ msg }}</h1>
<h2 :data-msg="msg"></h2>
<h3>threeeee</h3>
</template>
编译后的结果:
const __sfc__ = {}
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = ["data-msg"]
// 纯静态节点会被提升
const _hoisted_2 = /*#__PURE__*/_createElementVNode("h3", null, "threeeee", -1 /* HOISTED */)
function render(_ctx, _cache) {
// block
return (_openBlock(), _createElementBlock(_Fragment, null, [
// patchFlag: TEXT值为1
_createElementVNode("h1", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
// patchFlag: PROPS值为8
_createElementVNode("h2", { "data-msg": _ctx.msg }, null, 8 /* PROPS */, _hoisted_1),
_hoisted_2 // H3标签
], 64 /* STABLE_FRAGMENT */))
}
__sfc__.render = render
__sfc__.__file = "App.vue"
export default __sfc__
由于Block会收集所有动态子代节点,所以对动态节点的比对操作是忽略DOM层级结构的。这会带来额外的问题,即 v-if、v-for 等结构化指令会影响 DOM 层级结构,使之不稳定。这间接导致基于 Block 树的比对算法失效。而解决方式很简单,只需要让带有v-if、v-for 等指的节点也作为 Block 角色即可。
除了 Block 树以及补丁标志之外,Vue.js3在编译优化方面还做了其他努力,具体如下。
2. 静态提升:
能够减少更新时创建虚拟 DOM 带来的性能开销和内存占用。
<template>
<p>bbbb</p>
<p>cccc</p>
</template>
// 纯静态节点会被提升
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "bbbb", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", null, "cccc", -1 /* HOISTED */)
// render
function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_1,
_hoisted_2
], 64 /* STABLE_FRAGMENT */))
}
3. 预字符串化:
在静态提升的基础上,对静态节点进行字符串化。这样做能够减少创建虚拟节点产生的性能开销以及内存占用。
<template>
<p>ppp</p>
<p>ppp</p>
<p>ppp</p>
<p>ppp</p>
<p>ppp</p>
<p>ppp</p>
<p>ppp</p>
<p>ppp</p>
<p>ppp</p>
<div>ppp</div>
<p>ppp</p>
</template>
编译后生成的render函数:
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<p>ppp</p><p>ppp</p><p>ppp</p><p>ppp</p><p>ppp</p><p>ppp</p><p>ppp</p><p>ppp</p><p>ppp</p><div>ppp</div><p>ppp</p>", 11)
function render(_ctx, _cache) {
return _hoisted_1
}
4. 缓存内联事件处理函数
缓存内联事件处理函数:避免造成不必要的组件更新。
<script setup>
let clickFn = () => {
console.log('click h3');
}
</script>
<template>
<p @click="clickFn" >bbbb</p>
</template>
编译后生成的render函数:
setup(__props) {
let clickFn = () => {
console.log('click h3');
}
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("p", {
onClick: _cache[0] || (_cache[0] = (...args) => (_unref(clickFn) && _unref(clickFn)(...args)))
}, "bbbb"))
}
}
5. v-once 指令
v-once 指令缓存全部或部分虚拟节点,能够避免组件更新时重新创建虚拟 DOM 带来的性能开销,也可以避免无用的 Diff操作。