https://cn.vuejs.org/guide/best-practices/performance
Vue框架基于MVVM的架构进行开发
M - Model:数据层(数据逻辑处理
V - View: 视图层
VM - ViewModel: 数据的响应和渲染(连接数据层和视图层的桥梁)
Vue通过观察者模式和采用代理的方式进行数据劫持和数据响应,从而实现数据响应式
通过VNode、diff算法以及一些事件的特殊定制,eg: 特殊的指令操作和绑定操作,组件化处理进行模板渲染操作
再结合响应式系统、编译器联合 形成【数据驱动视图】的声明式框架
架构升级
Vue3从架构层面来说(基于跨平台的优化),在渲染器、响应式、编译器、组件化、运行时等进行拆分
渲染器优化:将渲染逻辑进行单独的封装
setup函数 =》 组合式API、生命周期
组合式API相较于Option API,代码逻辑更加聚合便于阅读、封装和复用
生命周期上,去掉create和beforeCreate将其封装到setup函数中执行
在setup中进行Vue的实例化
响应式系统
Vue2中通过Object.defineProperty劫持对象定义时的每一层属性,对于对象后续扩展的属性或数组索引上的变化无法做到响应式处理
Vue3中通过Proxy和Reflect对象对对象进行全面代理,解决Vue2中对象属性添加、数组长度变化、数组索引上变化无法响应式处理的缺点
此外还对ES6新增的两种数据结构Map和Set进行响应式处理
对于computed和watch的功能进行重新定义
在computed中通过缓存和调度器的方式实现懒值的处理
在watch中首先进行首次渲染默认不执行以及首次执行的watchEffect的定义
通过参数进行watch的设定包括回调函数的调度时机和watch数量,返回值可以控制watch的结束,内部进行响应式处理
对于组件之间的通讯进行emit和props的定义,单独封装API进行使用,包括对外暴露功能,通过expose导出,移除冗余的filters功能
在TS方面,Vue3本身使用ts进行编写支持TS语法
渲染器优化
patch函数的diff算法采用快速diff算法通过预处理和寻找最长递增子序列的算法
将在原先的双端diff算法的基础上,提升DOM操作性能
采用fragment片段的方式,通过新增fragment的元素类型,解决Vue2中只能进行一个根节点的操作,提升组件化的效率
新增teleport内置组件和异步组件的方式,提升在渲染方面的效率和多样化处理方式
对于v-if和v-for不能并存问题,由原先的v-for的优先级高于v-if改成v-if大于v-for
编译器优化
对于编译优化,在分析模板的过程中提取关键字信息patchFlag,将其存放在编译好的Vnode中
将节点分为动态节点和静态节点
对于动态节点生成一个block树,
对于静态节点进行静态提升,此外对于静态节点预字符串化和v-once的方法,减少DOM的创建和创建操作Vnode开销
tree-shaking,由于Vue3的拆分以及ESM的方式导入和导出,在打包过程中对未使用的代码进行清除,减小打包体积
Vue3.0性能提升通过那几个方面体现的? ===> 【响应式系统】【编译阶段】【打包体积上】
编译阶段优化
Vue2中每个组件实例都对应一个watch实例,在组件渲染过程中将用到的数据property记录为依赖
当依赖发生改变,触发setter会通知watch,使关联的组件重新渲染
Vue2中的问题案例
<template><div id="content"><p class="text">静态文本</p><p class="text">静态文本</p><p class="text">{{ message }}</p><p class="text">静态文本</p>...<p class="text">静态文本</p></div>
</template>
组件内部仅仅存在一个动态节点其他的都是静态节点,很多diff和遍历都是不需要会造成性能浪费
因此 Vue3在编译阶段做了进一步的优化:
- 静态提升
- diff算法优化 - PatchFlags
- Block Tree
- 事件监听缓存
- SSR优化
🎈1. 静态提升
Vue3中对不参与更新的元素进行静态提升,只会被创建依次,在渲染时直接复用
静态提升免去重复创建节点,免去重复创建的操作,优化运行时的内存占用
预字符串化(Pre-stringification):处理大量静态内容,可以将静态内容在编译时转换为字符串,以减少运行时的计算和处理
<template><div><h1>{{ title }}</h1><ul><li v-for="item in items" :key="item.id">{{ item.name }}</li></ul></div>
</template><script>
export default {data() {return {title: '静态标题',items: [{ id: 1, name: '静态项1' },{ id: 2, name: '静态项2' },{ id: 3, name: '静态项3' }]};}
};
</script>
在上面的示例中,title 和 items 是静态数据,不会在运行时发生变化。在编译时,Vue 3会将这些静态内容转换为字符串,以减少运行时的计算和处理。
预字符串化的结果如下所示:
const _hoisted_1 = { class: "title" };
const _hoisted_2 = { class: "item" };return (_ctx) => {return (_openBlock(),_createBlock("div", null, [_createVNode("h1", _hoisted_1, _toDisplayString(_ctx.title), 1 /* TEXT */),_createVNode("ul", null, [(_openBlock(true),_createBlock(_Fragment, null, _renderList(_ctx.items, (item) => {return (_openBlock(), _createBlock("li", _hoisted_2, _toDisplayString(item.name), 1 /* TEXT */));}), 256 /* UNKEYED_FRAGMENT */))])]));
};编译结果中,_hoisted_1和_hoisted_2两个常量存储静态内容的字符串化结果,在运行时只需要直接使用这些变量,而不需要进行额外的计算和处理
通过预字符串化,Vue3可以在需要的时候直接使用这些变量而不需要额外的计算和处理
静态提升细节:
- Vue2中,每次渲染时都会重新创建VNode节点,即使是静态节点也会被重新创建,导致一些非必要得消耗
- Vue3中,引入静态提升概念,将静态节点在编译阶段提升为常量,避免重复创建的开销
<span>你好</span>
<div>{{ message }}</div>没有做静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock(_Fragment, null, [_createVNode('span', null, '你好'),_createVNode('div', null, _toDisplayString(_ctx.message), 1)], 64))
}
// 静态提升之后
// 静态内容即不参与更新变化的节点_hoisted_1被放置在render函数外,每次渲染的时候只需要取_hoisted_1即可
// 同时_hoisted_1被打上PatchFlag,静态标记值为-1,特殊标志是负整数表示永远不会用于diff
const _hoisted_1 = _createVNode('span', null, '你好', -1)export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock(_Fragment, null, [_hoisted_1,_createVNode('div', null, _toDisplayString(_ctx.message), 1)],64))
}
🎈2. diff算法优化
Vue3在diff算法中相比于Vue2增加静态标记,
静态标记的作用:为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找到变化地方进行比较
对于已经标记静态节点的p标签在diff过程中则不会比较,性能进一步提高
- 静态类型枚举PatchFlags
export const enum PatchFlags {TEXT = 1,// 动态的文本节点CLASS = 1 << 1, // 2 动态的 classSTYLE = 1 << 2, // 4 动态的 stylePROPS = 1 << 3, // 8 动态属性,不包括类名和样式FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 FragmentKEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 FragmentUNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 FragmentNEED_PATCH = 1 << 9, // 512DYNAMIC_SLOTS = 1 << 10, // 动态 soltHOISTED = -1, // 特殊标志是负整数表示永远不会用作 diffBAIL = -2 // 一个特殊的标志,指代差异算法
}
🎈3. 事件监听缓存
默认情况下绑定事件行为被视为动态绑定,每次都会去追踪它的变化
在Vue2中,每次渲染时都会重新创建事件处理函数,即使相同的事件处理逻辑,会导致一些不必要的性能损耗
在Vue3中,引入缓存事件处理函数的概念,将事件处理函数在编译阶段缓存起来,避免重复创建的开销
<div><button @click="onClick">点我</button></div>// 未开启事件监听器缓存
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)))}, "点我")]))
}
🎈4. Block Tree ⭐⭐⭐
在Vue2中,模板中的条件循环和循环会导致大量的VNode节点创建和销毁,会影响渲染性能
在Vue3中,引入Block Tree的概念,将条件循环和循环渲染的内容封装为一个单独的Block,避免大量的VNode节点创建和销毁
Vue2和Vue3的编译结果对比
// Vue2的条件渲染
render(){return this.show ? createVNode("h1", null, "Hello World") : null// ...
}// Vue3的Block Tree
const _block_1 = this.show ? (openBlock(), createBlock("h1", null, "Hello World")) : null
function render(){return (_block_1)
}
通过使用Block Tree, Vue3将条件渲染和循环渲染的内容封装为一个单独的Block,避免大量的VNode节点创建和销毁从而提高渲染性能
在Vue2中,模板编译后会生成一个单一的渲染函数,该函数负责处理整个模板的渲染逻辑
每次更新时,整个模板都会重新渲染,即使其中只有一部分内容发生变化
在Vue3中,编译后的模板会被拆分为多个块(Blocks),每个块对应一个节点或一组节点。
这些块可以被独立地更新和渲染,从而避免不必要地渲染操作
Vue3的编译结果使用_createBlock和_createVNode来创建块和节点
这些块和节点可以被缓存起来,只有在需要更新时才会重新渲染
Vue3能够更加精确地追踪和更新变化地部分从而提高渲染性能。
当组件地状态发生变化时,只有受到影响的块和节点被重新渲染而不是整个模板。
Vue3的Block Tree在编译结果上和Vue2有所不同,通过拆分模块为多个块和节点,实现更细粒度的渲染更新从而提升性能和效率
🎈5. SSR优化
当静态内容达到一定量级,会使用createStaticVNode方法在客户端去生成一个static node,这些是静态node会被直接innerHTML
不需要创建对象,然后根据对象渲染
div><div><span>你好</span></div>... // 很多个静态属性<div><span>{{ message }}</span></div>
</div>// 对于大量的静态节点进行SSR优化
// 编译后
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><span>你好</span>...<div><span>你好</span><div><span>${_ssrInterpolate(_ctx.message)}</span></div></div>`)
}
响应式系统优化
Vue2中使用Object.defineProperty劫持定义时的对象,会深度递归遍历对象的每一层属性并添加setter()\getter()方法
在getter()方法中收集依赖,setter()方法中触发依赖更新
Vue3中使用Proxy和Reflect对象来代理整个对象是一种懒代理的方式不需要深度递归遍历同时可以解决Vue2中无法做到对象属性新增\删除、数组长度变化、索引值变化的响应式性
Vue3的响应式系统
- 可以监听对象动态属性的新增或删除
- 可以监听数组length属性和索引上值得变化
源码打包体积的优化 - Tree Shaking
相对于V2,V3整体体积变小,移除不常用的API以及利用Tree Shaking移除没有使用到的模块