上一章在patch阶段描述的内容,放在本章来了
什么是虚拟dom
我们在编写vue程序的时候,在页面上template标签里模拟html写法,游览器是不能识别.vue文件的,所以我们需要将模板中的代码转换为游览器能识别的代码,生成真实的dom结构.我们知道一个游览器dom是包含很多信息的,如果直接生成dom,操作dom,比较浪费性能.所以就有了虚拟dom(vnode).它是一个js对象,里面包含tag标签名,attrs属性,children子元素等.如果有dom发生变化,我们也只需要刷新虚拟dom,最后直接渲染页面即可.主要有
tag: 'div',
key: undefined,
elm: div#app, // 这是该vnode节点映射对应的真实dom节点
children: [
{
tag: undefined,
key: undefined,
elm: text // 文本节点
text: '1'
}
]
diff算法
vue中当数据发生变化时,我们需要重新渲染dom.但是他不是直接销毁整个dom树然后再重新渲染,这样十分浪费性能,他会生成一个新的虚拟dom树,然后比较新老虚拟dom,找到其中不同的地方,选择性的更新真实dom,以此来提升性能.
这个阶段是在patch中完成的,当我们数据发生变化时dop.notify会遍历watcher数组,通知所有的watcher数据发生变化,然后watcher就会调用patch给真实dom打补丁.我们的diff算法就是patch中的一个比较dom变化的规则.
根节点
diff算法比较的都是统计元素,不会跨级比较.首先比较2个虚拟dom的根元素的标签是否一样,如果根元素的标签不一样就直接移除dom,渲染新的dom,再比较根元素的其他属性是否有变化,如果发生了变化就更新相应的属性.
子节点
1.如果oldNode有children,newNode没有children,就删除真实dom的子节点. 2.如果oldNode没有children,newNode有children,就增加真实dom的子节点. 3.同级多节点,寻找节点唯一标识key,如果key相同代表改节点是可复用的,只用看他位置 有没有变化,如果变化了就更新位置就行. 双端diff,有时候这个位置移动是没有必要的.如abcd->dabc只需要移动d就可以了 没必要全部移动.这个时候vue就推出了双端diff,双端 diff 是头尾指针向中间移动的同时,对比头头、尾尾、头尾、尾头是否可以复用,如果可以的话就移动对应的 dom 节点.如果头尾没找到可复用节点就遍历 vnode 数组来查找,然后移动对应下标的节点到头部。
vue3优化
- PatchFlag:会给需要追踪的元素一个PatchFlag标记,代表这个元素可追踪,然后更新的的时候不用管其他的静态节点,只用管这些追踪节点就行,相比vue2对比所有节点节省了性能.
- 事件缓存:将事件缓存,onClick 会先读取缓存,如果缓存没有的话,就把传入的事件存到缓存里,都可以理解为变成静态节点了
- 长递增子序列优化了对比流程,在 Vue3 里 patchKeyedChildren 为 1头和头比,尾和尾比3.基于最长递增子序列进行移动/添加/删除 看个例子,比如 老的 children:[ a, b, c, d, e, f, g ] 新的 children:[ a, b, f, c, d, e, h, g ] 1、先进行头和头比,发现不同就结束循环,得到 [ a, b ] 2、再进行尾和尾比,发现不同就结束循环,得到 [ g ] 3、再保存没有比较过的节点 [ f, c, d, e, h ],并通过 newIndexToOldIndexMap 拿到在数组里对应的下标,生成数组 [ 5, 2, 3, 4, -1 ],-1 是老数组里没有的就说明是新增 4、然后再拿取出数组里的最长递增子序列,也就是 [ 2, 3, 4 ] 对应的节点 [ c, d, e ] 5、然后只需要把其他剩余的节点,基于 [ c, d, e ] 的位置进行移动/新增/删除就可以了