Vue3比Vue2快在哪里?

7/18/2020 vue3
  • Proxy 响应式
  • PatchFlag
  • hoistStatic
  • cacheHandel
  • SSR 优化
  • tree-shaking

# Proxy 响应式


  • 深度监听、性能更好
  • 可监听新增/删除属性
  • 可监听数组变化
  • Proxy 能规避 Object.defineProperty 的问题
  • Proxy 无法兼容所有浏览器,无法监听 Polyfill

# PatchFlag 动态标记


(vue2是全量比较)

  • 编译模板时,动态节点做标记
  • 标记,分为不同的类型
  • diff 算法时,可以区分静态节点,以及不同类型的动态节点
  • 静态提升
    • Vue2中无论元素是否参与更新,每次都会重新创建,然后在渲染。
    • Vue3中对于不参加更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
export const enum PatchFlags {
  TEXT = 1,// 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  表示只需要non-props修补的元素 (non-props不知道怎么翻才恰当~)
  DYNAMIC_SLOTS = 1 << 10,  // 1024 动态的solt
  DEV_ROOT_FRAGMENT = 1 << 11, //2048 表示仅因为用户在模板的根级别放置注释而创建的片段。 这是一个仅用于开发的标志,因为注释在生产中被剥离。
 
  //以下两个是特殊标记
  HOISTED = -1,  // 表示已提升的静态vnode,更新时调过整个子树
  BAIL = -2 // 指示差异算法应该退出优化模式
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# hoistStatic ( HOISTED = -1,)


  • 将静态节点的定义,提升到父作用域,缓存起来
  • 多个相邻的静态节点,会被合并
  • 拿空间换时间的优化策略
 // 将静态节点的定义,提升到父作用域,缓存起来 (三个相邻节点)

<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>

 HOISTED = -1,



import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "Hello World", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, "Hello World", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createElementVNode("div", null, "Hello World", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    _hoisted_1,
    _hoisted_2,
    _hoisted_3
  ], 64 /* STABLE_FRAGMENT */))
}

// Check the console for the AST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 多个相邻的静态节点,会被合并 (10个相邻节点)
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>


import { createElementVNode as _createElementVNode, createStaticVNode as _createStaticVNode } from "vue"

const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<div>Hello World</div><div>Hello World</div><div>Hello World</div><div>Hello World</div><div>Hello World</div><div>Hello World</div><div>Hello World</div><div>Hello World</div><div>Hello World</div><div>Hello World</div>", 10)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _hoisted_1
}

// Check the console for the AST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# cacheHandel 缓存事件


<div @click="handleClick">Hello World</div>


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)))
  }, "Hello World"))
}

// Check the console for the AST
1
2
3
4
5
6
7
8
9
10
11
12

# SSR 优化


  • 静态节点直接输出,绕过了vdom
  • 动态节点,需要动态渲染
// 渲染之前,静态节点提前优化,直接输出
<div>
  <div>Hello World</div>
  <div>Hello World</div>
  <div>Hello World</div>
  <span>{{msg}}</span>
</div>


import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "vue/server-renderer"

export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
  const _cssVars = { style: { color: _ctx.color }}
  _push(`<div${
    _ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
  }><div>Hello World</div><div>Hello World</div><div>Hello World</div><span>${
    _ssrInterpolate(_ctx.msg)
  }</span></div>`)
}

// Check the console for the AST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# tree-shaking

  • 编译时,根据不同情况,引入不同API,通过清除多于代码方式来优化项目打包体积(去除无用的代码)
  • 借助ES6模块的静态编译思想
    • 编译阶段,判断哪些模块已经加载
    • 判断哪些模块和变量未被使用或者引用,进而删除对应代码
  • 减少程序体积
  • 减少程序执行时间
  • 便于将来对程序架构进行优化
更新: 7/25/2022, 3:15:19 PM