使用 Proxy 实现响应式
vue3使用的 Proxy 在处理属性的读取和写入时,比vue2使用的defineProperty 有更好的性能(速度加倍的同时,内存还能减半!)
更新类型标记 Patch Flag
在编译模板时(将vue语法转换为js描述的虚拟节点vdom), vue3 对动态节点添加了标记,效果如下:
- 静态节点无标记
<!-- 静态节点 -->
<span>你好</span>
编译后
_createElementVNode("span", null, "你好")
- 动态文本节点会标记为
1/*TEXT*/
<span>{{msg}}</span>
编译后
_createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
更多vue3模板编译效果可通过下方网站预览
https://template-explorer.vuejs.org/
有了这些动态节点标记,vue3 在diff 算法时便可以跳过对不会发生变化的静态节点的比较,只比较可能发生变化的动态节点,从而提升了 diff 算法的性能,比需要比较静态节点的 vue2 更快!
缓存静态节点 hoistStatic
是一种典型的拿空间换时间的优化策略,具体操作如下:
- 将静态节点的定义,提升到父作用域,缓存起来(缓存后,就能在之后的编译中跳过编译,从而提升编译速度!)
<span>你好</span>
<span>{{msg}}</span>
编译后
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "你好", -1 /* HOISTED */)export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock(_Fragment, null, [_hoisted_1,_createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)], 64 /* STABLE_FRAGMENT */))
}
可见静态节点被定义为 _hoisted_1 提升到了父作用域,进行了缓存
- 对超过9个的相邻静态节点进行合并(合并后,优化了代码结构,减小了编译后代码的体积,实现了编译速度的提升!)
<span>你好1</span>
<span>你好2</span>
<span>你好3</span>
<span>你好4</span>
<span>你好5</span>
<span>你好6</span>
<span>你好7</span>
<span>你好8</span>
<span>你好9</span>
<span>你好10</span>
编译后
import { createElementVNode as _createElementVNode, createStaticVNode as _createStaticVNode } from "vue"const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<span>你好1</span><span>你好2</span><span>你好3</span><span>你好4</span><span>你好5</span><span>你好6</span><span>你好7</span><span>你好8</span><span>你好9</span><span>你好10</span>", 10)export function render(_ctx, _cache, $props, $setup, $data, $options) {return _hoisted_1
}
缓存事件 cacheHandler
也是一种典型的拿空间换时间的优化策略,具体操作如下:
- 给事件添加缓存(在之后的编译中跳过编译,提升编译速度!)
<button @click='save'>保存</button>
编译后
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock("button", {onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.save && _ctx.save(...args)))}, "保存"))
}
_cache[0] || (_cache[0] = ...
的部分,可见使用了缓存(已有缓存,则使用缓存,若无缓存,则重新编译)
SSR 优化
静态节点直接输出,绕过了 vdom
<span>你好</span>
<span>{{msg}}</span>
编译后
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(`<!--[--><span${_ssrRenderAttrs(_cssVars)}>你好</span><span${_ssrRenderAttrs(_cssVars)}>${_ssrInterpolate(_ctx.msg)}</span><!--]-->`)
}
动态节点还是需要动态渲染
tree-shaking
编译时,根据不同的情况,引入不同的 API
<span>你好</span>
编译后
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock("span", null, "你好"))
}
再增加一个动态节点
<span>你好</span>
<span>{{msg}}</span>
编译后
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock(_Fragment, null, [_createElementVNode("span", null, "你好"),_createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)], 64 /* STABLE_FRAGMENT */))
}
可见因增加了动态节点,增加了 import 的 API,如 createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, Fragment as _Fragment