Vue 3.0 编译做了哪些优化

diff方法优化

vue2.x中的虚拟dom是进行全量的对比。而vue3.0新增了静态标记PatchFlag。在与上次虚拟节点进行对比的时候,只对比带有PatchFlag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容

<div>
  <div>我是一个静态节点</div>
  <div>我是一个动态节点{{a + b}}</div>
</div>

编译后

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("div", null, "我是一个静态节点"),
    _createElementVNode("div", null, "我是一个动态节点" + _toDisplayString(_ctx.a + _ctx.b), 1 /* TEXT */)
  ]))
}

我们先看vue源码中对PatchFlag的定义

image.png

再看vue源码中对于createElementVNode的定义 image.png 可以看到带有{{a + b}}动态渲染的节点 编译成_createElementVNode的时候 第四个参数patchFlag为1,这就代表了这个节点在diff对比的时候需要参与对比

hoistStatic(静态提升)

vue2.x中无论元素是否参与更新,每次都会重新创建,然后再渲染。vue3.0中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可。
前面的编译没打开静态提升, 我们打开静态提升看一看

<div>
  <div>我是一个静态节点</div>
  <div>我是一个动态节点{{a + b}}</div>
</div>

编译后

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "我是一个静态节点", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _hoisted_1,
    _createElementVNode("div", null, "我是一个动态节点" + _toDisplayString(_ctx.a + _ctx.b), 1 /* TEXT */)
  ]))
}

// Check the console for the AST

可以看到静态节点被提取了出来 放在了render函数外面 命名为_hoisted_1 每次调用render函数的时候就不会再去执行_createElementVNode去创建节点 直接复用就可以了

cacheHandlers (事件侦听器缓存)

默认情况下,如onClick事件会被视为动态绑定,所以每次都会追踪它的变化,但是因为是同一个函数,所以不用追踪变化,直接缓存起来复用即可。

继续上代码

<div @click="handleClick" >我有一个onClick事件</div>

未开启事件侦听器缓存编译后

import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = ["onClick"]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", { onClick: _ctx.handleClick }, "我有一个onClick事件", 8 /* PROPS */, _hoisted_1))
}
// Check the console for the AST

可以看到编译后的代码_createElementBlock中patchFlag为8需要追踪对比,并且在_ctx上下文中获取事件handle

我们打开事件侦听器缓存再编译

import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", {
    onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
  }, "我有一个onClick事件"))
}

// Check the console for the AST

可以看到 patchFlag没有定义(patchFlag默认为0) 并且第一次从_ctx中获取到之后会缓存到_cache中, 后续从_cache中获取

目录