第十六 编译优化

96 阅读3分钟
  1. 动态节点收集和补丁标记
  2. block 树
  3. 静态提升
  4. 预字符串化
  5. 缓存内联事件处理函数
  6. v-once

1. 动态节点收集和补丁标记

Vue3编译优化指的是通过编译的手段提取关键信息,并以此指导生成最优代码的过程。具体来说,Vuejs3的编译器会充分分模板,提取关键信息并将其附着到对应的虚拟节点上。在运行时阶段,渲染器通过这些关键信执行“快捷路径”,从而提升性能。

0.jpeg

编译优化的核心在于,区分动态节点与静态节点。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操作。