前言
vue-next 对virtual dom的patch更新做了一系列的优化,从编译时加入了 block 以减少 vdom 之间的对比次数,另外还有 hoisted 的操作减少了内存的开销。本文写给自己看,做个知识点记录,如有错误,还请不吝赐教。
VDOM
VDOM的概念简单来说就是用js对象来模拟真实DOM树。由于MV**的架构,真实DOM树应该随着数据(Vue2.x中的data)的改变而发生改变,这些改变可能是以下几个方面:
- v-if
- v-for
- 动态的props(如:class,@click)
- 子节点的改变
- 等等
Vue框架要做的其实很单一:在用户改变数据时,正确更新DOM树,做法就是其核心的VDOM的patch和diff算法。
Vue2.x中的做法
在Vue2.x中,当数据改变后就要对所有的节点进行patch和diff操作。如以下DOM结构:
<div> <span class="header">I'm header</span> <ul> <li>第一个静态li</li> <li v-for="item in mutableItems" :key="item.key"> {{ item.desc }}</li> </ul> </div>
在第一次mount节点的时候会去生成真实的DOM,此后如果
mutableItems.push({ key: 'asdf', desc: 'a new li item' })
预期的结果是页面出现新的一个li元素,内容就是 a new li item,Vue2.x中是通过patch时对 ul 元素对应的 vnode 的 children 来进行 diff 操作,具体操作在此不深究,但是该操作是需要比较所有的 li 对应的 vnode 的。
不足
正是由于2.x版本中的diff操作需要遍历所有元素,本例中包括了 span 和 第一个li元素,但是这两个元素是静态的,不需要被比较的,不论数据怎么变,静态元素都不会再更改了。vue-next在编译时对这种操作做了优化,即 Block。
Block
入上述模板,在vue-next中生成的渲染函数为:
const _Vue = Vue const { createVNode: _createVNode } = _Vue const _hoisted_1 = _createVNode("span", { class: "header" }, "I'm header", -1 /* HOISTED */) const _hoisted_2 = _createVNode("li", null, "第一个静态li", -1 /* HOISTED */) return function render(_ctx, _cache) { with (_ctx) { const { createVNode: _createVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString } = _Vue return (_openBlock(), _createBlock(_Fragment, null, [ _hoisted_1, _createVNode("ul", null, [ _hoisted_2, (_openBlock(true), _createBlock(_Fragment, null, _renderList(state.mutableItems, (item) => { return (_openBlock(), _createBlock("li", { key: item.key }, _toDisplayString(item.desc), 1 /* TEXT */)) }), 128 /* KEYED_FRAGMENT */)) ]) ], 64 /* STABLE_FRAGMENT */)) } }
我们可以看到调用了 openBlock 和 createBlock 方法,这两个方法的代码实现也很简单:
const blockStack: (VNode[] | null)[] = [] let currentBlock: VNode[] | null = null let shouldTrack = 1 // openBlock export function openBlock(disableTracking = false) { blockStack.push((currentBlock = disableTracking "htmlcode">// createVNode function _createVNode( type: VNodeTypes | ClassComponent, props: (Data & VNodeProps) | null = null, children: unknown = null, patchFlag: number = 0, dynamicProps: string[] | null = null ) { /** * 一系列代码 **/ // presence of a patch flag indicates this node needs patching on updates. // component nodes also should always be patched, because even if the // component doesn't need to update, it needs to persist the instance on to // the next vnode so that it can be properly unmounted later. if ( shouldTrack > 0 && currentBlock && // the EVENTS flag is only for hydration and if it is the only flag, the // vnode should not be considered dynamic due to handler caching. patchFlag !== PatchFlags.HYDRATE_EVENTS && (patchFlag > 0 || shapeFlag & ShapeFlags.SUSPENSE || shapeFlag & ShapeFlags.STATEFUL_COMPONENT || shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) ) { currentBlock.push(vnode) } }上述函数是在模板编译成ast之后调用的生成VNode的函数,所以有patchFlag这个标志,如果是动态的节点,并且此时是开启了Block的话,就会将节点塞入Block中,这样 createBlock返回的 VNode 中就会有 dynamicChildren 了。
到此为止,通过本文中案例经过模板编译和render函数运行后并经过了优化以后生成了如下结构的vnode:
const result = { type: Symbol(Fragment), patchFlag: 64, children: [ { type: 'span', patchFlag: -1, ...}, { type: 'ul', patchFlag: 0, children: [ { type: 'li', patchFlag: -1, ...}, { type: Symbol(Fragment), children: [ { type: 'li', patchFlag: 1 ...}, { type: 'li', patchFlag: 1 ...} ] } ] } ], dynamicChildren: [ { type: Symbol(Fragment), patchFlag: 128, children: [ { type: 'li', patchFlag: 1 ...}, { type: 'li', patchFlag: 1 ...} ] } ] }以上的 result 不完整,但是我们暂时只关心这些属性。可以看见 result.children 的第一个元素是span,patchFlag=-1,且 result 有一个 dynamicChildren 数组,里面只包含了两个动态的 li,后续如果变动了数据,那么新的 vnode.dynamicChildren 会有第三个 li 元素。
patch
patch部分其实也没差多少,就是根据vnode的type执行不同的patch操作:
function patchElement(n1, n2) { let { dynamicChildren } = n2 // 一系列操作 if (dynamicChildren) { patchBlockChildren ( n1.dynamicChildren!, dynamicChildren, el, parentComponent, parentSuspense, areChildrenSVG ) } else if (!optimized) { // full diff patchChildren( n1, n2, el, null, parentComponent, parentSuspense, areChildrenSVG ) } }可以看见,如果有了 dynamicChildren 那么vue2.x版本中的diff操作就被替换成了 patchBlockChildren() 且参数只有 dynamicChildren,就是静态的不做diff操作了,而如果vue-next的patch中没有 dynamicChildren,则进行完整的diff操作,入注释写的 full diff 的后续代码。
结尾
本文没有深入讲解代码的实现层面,一是因为自己实力不济还在阅读源码当中,二是我个人认为阅读源码不可钻牛角尖,从大局入眼,再徐徐图之,先明白了各个部分的作用后带着思考去阅读源码能收获到的应该更多一些。
Vue3,VDOM
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新日志
- 第五街的士高《印度激情版》3CD [WAV+CUE][2.4G]
- 三国志8重制版哪个武将智力高 三国志8重制版智力武将排行一览
- 三国志8重制版哪个武将好 三国志8重制版武将排行一览
- 三国志8重制版武将图像怎么保存 三国志8重制版武将图像设置方法
- 何方.1990-我不是那种人【林杰唱片】【WAV+CUE】
- 张惠妹.1999-妹力新世纪2CD【丰华】【WAV+CUE】
- 邓丽欣.2006-FANTASY【金牌大风】【WAV+CUE】
- 饭制《黑神话》蜘蛛四妹手办
- 《燕云十六声》回应跑路:年内公测版本完成95%
- 网友发现国内版《双城之战》第二季有删减:亲亲环节没了!
- 邓丽君2024-《漫步人生路》头版限量编号MQA-UHQCD[WAV+CUE]
- SergeProkofievplaysProkofiev[Dutton][FLAC+CUE]
- 永恒英文金曲精选4《TheBestOfEverlastingFavouritesVol.4》[WAV+CUE]
- 群星《国风超有戏 第9期》[320K/MP3][13.63MB]
- 群星《国风超有戏 第9期》[FLAC/分轨][72.56MB]