都说Vue3跟Vue2比,性能优化很厉害!

都说Vue3跟Vue2比,性能优化很厉害!template 模板不如 jsx 灵活 但是 template 相比 jsx 的固定性 可以在编译时获取许多信息 编译出可以在运行时执行尽可能少 性能尽可能好的代码 Vue3 性能优化的一个重要体现在编译优化 利用新的渲染器 编译出了相比 vue2 更小 更

大家好,欢迎来到IT知识分享网。

template模板不如jsx灵活,但是template相比jsx的固定性,可以在编译时获取许多信息,编译出可以在运行时执行尽可能少,性能尽可能好的代码。

Vue3性能优化的一个重要体现在编译优化,利用新的渲染器,编译出了相比vue2更小,更快的代码。

Tree Shaking – 优化体积

Vue3 源码中采用函数编写API,更加有利于Tree Shaking,而Tree Shaking的原理是利用ES6 Module的编译时加载,编译时就能确定模块的依赖关系,没有使用到的代码最终会被 webpack 或者 vite这样的构建工具删掉,js体积减小,网络传输就更快,js引擎解析也会更快,代码执行更快。

vue2项目打包体积对比

// App.vue 1 
    // App.vue 2 
   

打包后vue文件大小没有变化

都说Vue3跟Vue2比,性能优化很厉害!

Vue3项目打包体积对比

// App.vue 1    

打包vue文件大小有变化

都说Vue3跟Vue2比,性能优化很厉害!

Poxy – 优化数据劫持

vue2的数据劫持使用的是 Object.defineProperty,它的缺点也是众所周知,只能监听对象中已有的属性,不能监听对象的增加删除,所以如果有一个嵌套层级很深的响应式对象数据,vue2无法知道代码运行时具体会访问哪个属性,所以在初始化这个对象的时候,vue2只能采取递归遍历的方式把对象的每一层每一个属性都变成响应式,这就会影响页面的初始化渲染速度;

而vue3就不一样了,它使用proxy进行数据劫持,对于多层嵌套的对象,由于proxy只能代理一层,所以vue3在真正访问到对象属性的时候,才去判断递归,而不是在初始化的时候就一股脑的递归。

下面看一下vue2和vue3在源码中的实现

vue2源码实现

function initData(vm: Component) { let data: any = vm.$options.data // 观测 data observe(data) } export function observe( value: any, shallow?: boolean, ): Observer | void { new Observer(value, shallow) } export class Observer { constructor( public value: any, public shallow = false // 默认深层响应 ) { const keys = Object.keys(value); // 遍历每一个属性变成响应式 for (let i = 0; i < keys.length; i++) { const key = keys[i]; defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow); } } } export function defineReactive( obj: object, key: string, val?: any, customSetter?: Function | null, shallow?: boolean, ) { val = obj[key] // 递归遍历,嵌套过深,性能损失 !shallow && observe(val, false, mock) //... }

vue3源码实现

// 简化版源码 // ref() ref也是包装过后的reactive export function ref(value?: unknown) { return createRef(value) } function createRef(rawValue: unknown) { return new RefImpl(rawValue) } class RefImpl 
  
    { private _value: T constructor(value: T) { this._value = reactive(value) } get value() { return this._value } set value(newVal) { this._value = reactive(newVal) } } // reactive() export function reactive(target: object) { return createReactiveObject(target) } function createReactiveObject(target: Target) { const proxy = new Proxy(target, { get(target: Target, key: string | symbol) { const res = Reflect.get(target, key); if (isObject(res)) { // 对象属性被访问的时候才递归执行下一步 reactive, // 优化数据初始化时性能 return reactive(res); } return res; }, }); return proxy; } 
  

编译优化

静态提升

vue3将模版中的静态节点和属性提取到render函数外面,在组件更新的时候,减少vnode的创建带来的性能损耗

// App.vue  
  
都说Vue3跟Vue2比,性能优化很厉害!

预字符串化

当有大量连续的静态节点时,通过转化为字符串,既减少vnode创建过程,也可以减少代码体积

// App.vue  
  
都说Vue3跟Vue2比,性能优化很厉害!

缓存事件处理函数

每次render函数执行过后,生成新的vnode,对vnode的props中事件属性进行patch的时候,就直接取上一次缓存的函数,如果没有缓存,每次函数都是新的,引用不一致,会造成组件的更新

 
  
都说Vue3跟Vue2比,性能优化很厉害!

Block Tree

Block是vue3在编译模板过程中做的优化,收集动态子节点,能够在diff过程中根据动态子节点数量更新。

 
  

在浏览器控制台Network中可以看到模板被编译后

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, } from "/node_modules/.vite/deps/vue.js?v=6f26e7ed"; const _hoisted_1 = { class: "block" }; const _hoisted_2 = /*#__PURE__*/ _createElementVNode( "h1", null, "Block", -1 /* HOISTED */ ); function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return ( _openBlock(), _createElementBlock("div", _hoisted_1, [ _hoisted_2, _createElementVNode( "span", null, _toDisplayString($setup.msg), 1 /* TEXT */ ), ]) ); }

render函数中调用了3个函数,openBlockcreateElementBlockcreateElementVNode,通过这个三个函数收集动态子节点

// /packages/runtime-core/src/vnode.ts // 存储currentBlock数组 export const blockStack: (VNode[] | null)[] = [] // 当前block export let currentBlock: VNode[] | null = null // 向blockStack推入currentBlock export function openBlock(disableTracking = false) { blockStack.push((currentBlock = disableTracking ? null : [])) } export function createElementBlock( type: string | typeof Fragment, props?: Record 
  
    | null, children?: any, patchFlag?: number, dynamicProps?: string[], shapeFlag?: number ) { return setupBlock( createBaseVNode( type, props, children, patchFlag, dynamicProps, shapeFlag, true /* isBlock */ ) ) } function createBaseVNode(type, props = null, children = null,patchFlag = 0) { const vnode = { type, props, children, patchFlag, // ... }; return vnode; } function setupBlock(vnode: VNode) { // 在vnode上保留当前Block收集的动态子节点 vnode.dynamicChildren = isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null return vnode } 
  

例子中的render函数执行后返回一个vnode对象,如下,有typechildrendynamicChildrenprops等属性

都说Vue3跟Vue2比,性能优化很厉害!

将图中的vnode对象简化一下,

{ type: "div", props: { class: "block", }, children: [ { type: "h1", children: "Block", }, { type: "span", children: "vue", }, ], dynamicChildren: [ { type: "span", children: "vue", }, ], };

更新的时候,就会根据vnode中的数据进行diff,在组件更新逻辑中,组件的更新最终还是会走到对普通 DOM 元素的更新,

// /packages/runtime-core/src/renderer.ts const patch = (n1, n2, container, anchor = null, parentComponent = null) => { const { type, ref, shapeFlag } = n2; switch (type) { if (shapeFlag & ShapeFlags.ELEMENT) { // 更新普通 DOM 元素 processElement(n1, n2, container, anchor, parentComponent); } else if (shapeFlag & ShapeFlags.COMPONENT) { // 更新组件 processComponent(n1, n2, container, anchor, parentComponent); } } }; const processElement = (n1, n2, container, anchor, parentComponent) => { if (n1 == null) { // 挂载 } else { // 更新 patchElement(n1,n2,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized); } }; 

组件是抽象的普通Dom元素的集合,更新最终都会走到 patchElement 这个函数,

// /packages/runtime-core/src/renderer.ts const patchElement = ( n1: VNode, n2: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { const el = (n2.el = n1.el!); let { patchFlag, dynamicChildren, dirs } = n2; if (dynamicChildren) { // 如果有dynamicChildren,只更新动态子节点 } else if (!optimized) { // 全量更新所有子节点 }

PatchFlag

vue2 对比节点时,不知道这个节点哪些信息发生了变化,只能依次对比这些信息,vue3中,收集了dynamicChildren,已经减少对比静态子节点了,但是,动态子节点有许多属性,配合使用patchFlag,就可以知道哪些属性需要更新,就可以实现靶向更新

vue3中patchFlag是包含一系列二进制操作值的枚举类型,

// /packages/shared/src/patchFlags.ts export const enum PatchFlags { // 动态文本的元素 TEXT = 1, //0b0000001 1 // 动态 class 的元素 CLASS = 1 << 1, //0b0000010 2 // 动态 style 的元素 STYLE = 1 << 2, //0b0000100 4 // 动态 props 的元素 PROPS = 1 << 3, //0b0001000 8 // 动态props和有key值绑定的元素 FULL_PROPS = 1 << 4, //0b0010000 16 // 静态节点 HOISTED = -1, //... }

认识一下跟二进制相关的几个操作符:

左移操作符 (<<),是将第一个操作数向左移动指定位数,左边超出的位数将会被清除,右边将会补零

按位与( &)运算符在两个操作数对应的二进位都为 1 时,该位的结果值才为 1;

按位或(| )运算符在其中一个或两个操作数对应的二进制位为 1 时,该位的结果值为 1。

patchFlag是在创建vnode的时候作为第四个参数传入,如下图

 
  
都说Vue3跟Vue2比,性能优化很厉害!

在 patchElement 对普通Dom元素进行更新的时候,就可以做到只对动态有变化的属性更新

// /packages/runtime-core/src/renderer.ts const patchElement = ( n1: VNode, n2: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { const el = (n2.el = n1.el!); let { patchFlag, dynamicChildren, dirs } = n2; patchFlag |= n1.patchFlag & PatchFlags.FULL_PROPS; const oldProps = n1.props || EMPTY_OBJ; const newProps = n2.props || EMPTY_OBJ; if (dynamicChildren) { // 如果有dynamicChildren,只更新动态子节点 } else if (!optimized) { // 全量更新所有子节点 } if (patchFlag > 0) { if (patchFlag & PatchFlags.FULL_PROPS) { // 如果元素的 props 中含有动态的 key,则需要全量比较 props } else { if (patchFlag & PatchFlags.CLASS) { if (oldProps.class !== newProps.class) { // 有动态的class, 更新class属性 } } if (patchFlag & PatchFlags.STYLE) { // 有动态的style, 更新style属性 } if (patchFlag & PatchFlags.PROPS) { // 除了class和style外,其他动态的 prop 或者 attrs const propsToUpdate = n2.dynamicProps!; for (let i = 0; i < propsToUpdate.length; i++) { // 遍历更新 } } } if (patchFlag & PatchFlags.TEXT) { if (n1.children !== n2.children) { // 更新动态的文本 } } } else if (!optimized && dynamicChildren == null) { // 全量比较 props } };

都说Vue3跟Vue2比,性能优化很厉害!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/89477.html

(0)
上一篇 2026-04-04 20:01
下一篇 2026-01-25 08:46

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信