Vue3 性能提升主要是通过哪几个方面

1. 编译阶段

回顾 Vue 2,我们知道每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把用到的数据 property 记录为依赖。当依赖发生改变,触发 setter,则会通知 watcher,从而使关联的组件重新渲染。

<div id="content">
  <p class="text">静态文本</p>
  <p class="text">{{ message }}</p>
  <p class="text">静态文本</p>
  ...
  <p class="text">静态文本</p>
</div>

组件内部只有一个动态节点,剩余的都是静态节点。这里很多 diff 和遍历其实都是不需要的,造成性能浪费。

因此,Vue 3 在编译阶段做了进一步优化。主要有如下:

  • vue3 在 diff 算法中相比 vue2 增加了静态标记(PatchFlag)。标记静态节点的 p 标签在 diff 过程中不会比较,把性能进一步提高。

静态类型枚举

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,    // 动态 slot
  HOISTED = -1,   // 特殊标志,负整数表示永远不会用作 diff
  BAIL = -2       // 一个特殊的标志,指代差异算法
}

静态提升

Vue3 中对不参与更新的元素会做静态提升,只会被创建一次,在渲染时直接复用。这样就免去了重复的创建节点,大型应用会受益于这个改动,优化了运行时的内存占用。

const _hoisted_1 = /*#__PURE__*/_createVNode('span', null, '你好', -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _hoisted_1,
    _createVNode('div', null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ], 64 /* STABLE_FRAGMENT 静态标记 64 */))
}

静态内容 _hoisted_1 被放置在 render 函数外,每次渲染时只要取 _hoisted_1 即可。

事件监听缓存

默认情况下,绑定事件行为会被视为动态绑定,所以每次都会去追踪它的变化。

未开启事件监听器缓存:

export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock('div', null, [
    _createVNode('button', { onClick: _ctx.onClick }, '点我', 8 /* PROPS */, ['onClick'])
    // PROPS=1<<3, // 8 // 动态属性,但不包含类名和样式
  ]))
})

开启事件侦听缓存:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock('div', null, [
    _createVNode('button', {
      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
    }, '点我')
  ]))
}

开启缓存后,没有了静态标记。下次 diff 算法时直接使用。

大块静态内容 createStaticVNode

当静态内容大到一定量级时,会用 createStaticVNode 方法在客户端生成一个 static node。这些静态 node 会被直接 innerHTML,不需要创建对象再根据对象渲染。

2. 源码体积变小(摇树 tree shaking)

相比 Vue 2,Vue 3 整体体积变小了。除了移除一些不常用的 API,更重要的是 Tree shaking。

任何一个函数(如 ref、reactive、computed 等)仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小。

3. 响应式系统重写

  • vue2 中采用 defineProperty 来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加 getter 和 setter,实现响应式。
  • vue3 采用 proxy 重写了响应式系统。因为 proxy 可以对整个对象进行监听,所以不需要深度遍历。

来源整理自:vue3js.cn 面试官系列