vue2升级vue3:vue3比vue2究竟好在哪里?
Author:zhoulujun Date:
vue2升级vue3 已经做了一段时间了,官方说性能提升2-3呗。实际上并没有那么夸张。
More efficient ref implementation (~260% faster read / ~50% faster write)
~40% faster dependency tracking
~17% less memory usage
vue2升级vue3,不想react 丝滑升级。代价还是不少。如果只是单纯提升性能,目前代价还是太大。
如果是新项目,直接用vue3做更好。
编译时对VDom的性能优化
对vue有点基础的应该都知道vue生成dom的方式是通过 ’虚拟dom‘ 渲染 。下面我通过代码简单来阐述下虚拟dom是什么?
<div class='xxhh'>哈哈<div> <div>{{msg}}</div>
上面是很简单的两个html,vue会将这段dom转换成js代码,“可能”如下所示:
[{ html:'div', text:'哈哈', class:'xxhh', children:null }, { html:'div', text:'$msg', children:null }]
生成的这段跟dom结构一样的树形js代码就是虚拟dom了。
把虚拟dom树交给一个render()函数处理一下就能生成我们肉眼可见的html了。
那Diff()算法是用来干嘛的呐?
简单点说:diff算法用于比较js树的更新,当执行一次更新模板的操作后,diff算法用于比较更新的内容是什么,并告诉render()函数哪里需要更新。比如当msg变量更新后,diff算法就会通过全量比较得出一个结论,msg变了,所以跟msg相关的dom节点都需要更新。diff算法的作用我们已经知道了,用代码实现如下:
function diff(oldDom,newDom){ // oldDom 和 newDom 一一比较 return needUpdateDom }
谷歌一下,资料不要太多: https://www.google.com/search?q=vue3+diff+源码&oq=vue3++diff+源码
diff算法优化
了解过Vue2的朋友们肯定知道diff算法,所谓diff算法是Vue将模板编译成渲染函数生成虚拟DOM树,通过递归遍历两个虚拟DOM树,并比较每个节点上的每个属性,来确定实际DOM哪些部分需要更新。虽然经过现代JS引擎执行的高级优化之后,这种算法非常快速,但是在实际使用中DOM的更新仍然设计到许多不必要的工作(具体表现在静态内容,只有少量动态模板的情况)。
比如下面这段代码
<div> <p>老八食堂</p> <p>{{ message }}</p> </div>
vue2的diff全量对比:
在之前的VDOM中,如果msg值发生改变,整个模版中的所有元素都需要重新渲染。
而在Vue3中使用patchFlag(静态标记)对diff算法进行了优化,在创建虚拟DOM时,根据DOM内容是否会发生变化,而给与相应类型的patchFlag。在更新前节点进行比较的时候,只会去对比带有静态标记的节点
vue3 patchFlag 优化
通过上图,Vue3的diff算法只对带有静态标记的DOM进行比较,只需要对比一次,而Vue2中的diff算法却比较了3次。这便是Vue3比Vue2性能好的一个原因。
接下来我们通过模板转化网站 把模板转化成虚拟DOM来验证一些上述分析:
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, [ _createElementVNode("p", null, "老八食堂"), _createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */) ])) }
message对应的P标签有个注释为TEXT的1,这个1就是patchFlag。
Vue3.0在模版编译时,编译器会在动态标签末尾加上 /* Text*/ PatchFlag。只能带patchFlag 的 Node 才被认为是动态的元素,会被追踪属性的修改。并且 PatchFlag 会标识动态的属性类型有哪些,比如这里 的TEXT 表示只有节点中的文字是动态的。
每一个Block中的节点,就算很深,也是直接跟Block一层绑定的,可以直接跳转到动态节点而不需要逐个逐层遍历。
既有VDOM的灵活性,又有性能保证。
而Vue3对不同类型的元素会有不同的flag标记
再来看一段很常见的代码
<divid="app"> <h1>hello world</h1> <p>今天天气真不错</p> <div>{{name}}</div> </div>vue2中会解析
function render(){ with (this) { return _c('div', { attrs: { 'id': 'app' } }, [_c('h1', [_v('hello world')]), _c('p', [_v('今天天气真不错')]), _c('div', [_v( _s(name))])]) } }
其中前面两个标签是完全静态的,后续的渲染中不会产生任何变化, Vue2中依然使用 _c新建成 vdom,在 diff的时候需要对比,有一些额外的性能损耗
我们看下vue3中的解析结果
import{ createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" exportfunction render(_ctx, _cache){ return(_openBlock(), _createBlock("div",{ id:"app"},[ _createVNode("h1",null,"hello world"), _createVNode("p",null,"今天天气真不错"), _createVNode("div",null, _toDisplayString(_ctx.name),1/* TEXT */) ])) } // Check the console for the AST给更多查看:vue2、vue3区别虚拟dom和Slot性能提升的原理 www.lucklnk.com/godaddy/details/aid/179291165
再diff的时候,只需要对比 text或者 props,不用再做无畏的 props遍历
这里安利一些:
vue2、vue3 diff 算法源码解析 https://juejin.cn/post/7100092670566989861
vue3 diff核心源码剖析 https://juejin.cn/post/7080357950040047653
Vue3源码分析-完整update流程和diff算法 https://juejin.cn/post/6962702898790662152
深入浅出虚拟 DOM 和 Diff 算法,及 Vue2 与 Vue3 中的区别 https://juejin.cn/post/7010594233253888013
Vue3源码中的Flag类型
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, // 动态 solt HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff BAIL = -2 // 一个特殊的标志,指代差异算法 }
如果同时有 props和 text的绑定呢, 位运算组合即可。
<divid="app"> <h1>摸金校尉</h1> <p>今天天气真不错</p> <div :id="userid"">{{name}}</div> </div>
编译代码
import{ createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" exportfunction render(_ctx, _cache){ return(_openBlock(), _createBlock("div",{ id:"app"},[ _createVNode("h1",null,"摸金校尉"), _createVNode("p",null,"今天天气真不错"), _createVNode("div",{ id: _ctx.userid, "\"":"" }, _toDisplayString(_ctx.name),9/* TEXT, PROPS */,["id"]) ])) } // Check the console for the AST
text是1, props是8,组合在一起就是9,我们可以简单的通过位运算来判定需要做 text和 props的判断, 按位与即可,只要不是0就是需要比较
位运算来做类型组合 本身就是一个最佳实践,react大兄弟也是一样的 代码
exportconst PLUGIN_EVENT_SYSTEM =1; exportconst RESPONDER_EVENT_SYSTEM =1<<1; exportconst USE_EVENT_SYSTEM =1<<2; exportconst IS_TARGET_PHASE_ONLY =1<<3; exportconst IS_PASSIVE =1<<4; exportconst PASSIVE_NOT_SUPPORTED =1<<5; exportconst IS_REPLAYED =1<<6; exportconst IS_FIRST_ANCESTOR =1<<7; exportconst LEGACY_FB_SUPPORT =1<<8;
hoistStatic 静态提升
我们在平时开发中,会将一些常量提升出去定义,如下所示:
const PAGE_SIZE = 20 function getDate() { $.get({ url: '', data: { pageSize: PAGE_SIZE } }) }
如上面的例子,如果我们将PAGE_SIZE写在getData内,每次调用都会重新定义一次PAGE_SIZE变量。
在Vue3中也做了类似上面的优化,Vue3的编译器会将模板中的静态节点、子树、数据对象,并在生成的代码中将他们提升到渲染函数之外,一次来避免每次调用渲染函数的时候重新创建这些对象,从而大大提高内存使用率并减少垃圾回收频率。
当使用hoistStatic时,所有 静态的节点都被提升到render方法之外。这意味着,他们只会在应用启动的时候被创建一次,而后随着每次的渲染被不停的复用
在大型应用中对于内存有很大的优化。
上图中红框圈起来的地方,将模板中的静态模板提升到了render函数外,这又提升了Vue的性能。
cacheHandlers 事件监听缓存
正常情况下,当绑定一个事件:
<div> <p @click="handleClick">静态代码</p> </div>
模版会被编译为
export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", { onClick: _ctx.handleClick }, "静态代码", 8 /* PROPS */, ["onClick"]) ])) }
其中事件会每次从全局上下文中获取。而当开启了cacheHandler之后
export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", { onClick: _cache[1] || (_cache[1] = ($event, ...args) => (_ctx.handleClick($event, ...args))) }, "静态代码") ])) }
编辑器会为你动态创建一个内联函数,内联函数里面再去引用当前组件上最新的handler。之后编辑器会将内联函数缓存。每次重新渲染时如果事件处理器没有变,就会使用缓存中的事件处理而不会重新获取事件处理器。这个节点就可以被看作是一个静态的节点。
传入的事件会自动生成并缓存一个内联函数再cache里,变为一个静态节点。这样就算我们自己写内联函数,也不会导致多余的重复渲染
这种优化更大的作用在于当其作用域组件时,之前每次重新渲染都会导致组件的重新渲染,在通过handler缓存之后,不会导致组件的重新渲染了。
StaticNode 静态节点
当静态节点嵌套足够多的时候,VUE编译器也会将VDOM转化为纯字符串的HTML。即 StaticNode。
每个元素都会变成虚拟节点,一大堆的虚拟节点
转载本站文章《vue2升级vue3:vue3比vue2究竟好在哪里?》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue3/8863.html