返回
Vue2深入浅出-虚拟DOM优化手段全面了解
前端
2023-09-16 23:08:32
在上一篇文章《虚拟DOM之移动》中,我们介绍了一个简单的虚拟DOM Diff算法。在这篇文章中,我们将主要介绍针对它的优化。
场景
考虑下面的场景:
<div id="app">
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
</div>
按照《虚拟DOM之移动》中的算法,遍历newVnode:
- 首先比较newVnode[0]和oldVnode[0],发现它们是相同的,不需要移动。
- 然后比较newVnode[1]和oldVnode[1],发现它们是不同的,需要移动。
- 接下来比较newVnode[2]和oldVnode[2],发现它们是相同的,不需要移动。
在这个过程中,我们一共移动了一个节点。
优化
现在,我们来考虑一下如何优化这个算法。
新旧节点对比
在比较新旧节点时,我们可以先比较它们的key值。如果key值相同,则说明这两个节点是同一个节点,不需要移动。
if (newVnode.key === oldVnode.key) {
return;
}
节点复用
如果新旧节点的key值不同,则需要移动节点。但是,在移动节点之前,我们可以先尝试复用旧节点。
let oldNode = oldVnode.elm;
if (oldNode && isSameNodeType(oldNode, newVnode)) {
patch(oldVnode, newVnode);
return;
}
如果旧节点和新节点是相同的类型,则我们可以直接复用旧节点,只需要更新它的属性和子节点即可。
Diff算法优化
在比较新旧节点的子节点时,我们可以使用一种更快的算法。这种算法叫做“双指针算法”。
双指针算法的原理是,从新旧节点的第一个子节点开始,同时遍历两个节点的子节点。如果两个子节点是相同的,则继续比较下一个子节点。如果两个子节点不同,则移动新节点的子节点到旧节点的子节点之前。
let newStart = newVnode.firstChild;
let newEnd = newVnode.lastChild;
let oldStart = oldVnode.firstChild;
let oldEnd = oldVnode.lastChild;
while (newStart && oldStart && isSameVnode(newStart, oldStart)) {
patch(newStart, oldStart);
newStart = newStart.nextSibling;
oldStart = oldStart.nextSibling;
}
while (newEnd && oldEnd && isSameVnode(newEnd, oldEnd)) {
patch(newEnd, oldEnd);
newEnd = newEnd.previousSibling;
oldEnd = oldEnd.previousSibling;
}
减少移动次数
在移动节点时,我们可以尽量减少移动的次数。一种方法是,将要移动的节点放到一个数组中,然后一次性移动所有节点。
let toMove = [];
while (newStart) {
if (!oldStart) {
toMove.push(newStart);
} else if (!isSameVnode(newStart, oldStart)) {
toMove.push(newStart);
oldStart = oldStart.nextSibling;
} else {
oldStart = oldStart.nextSibling;
}
newStart = newStart.nextSibling;
}
while (newEnd) {
if (!oldEnd) {
toMove.push(newEnd);
} else if (!isSameVnode(newEnd, oldEnd)) {
toMove.push(newEnd);
oldEnd = oldEnd.previousSibling;
} else {
oldEnd = oldEnd.previousSibling;
}
newEnd = newEnd.previousSibling;
}
for (let i = 0; i < toMove.length; i++) {
insertBefore(toMove[i], oldStart);
}
总结
通过以上优化,我们可以大大提高虚拟DOM的移动性能。在实际项目中,我们可以根据需要选择不同的优化策略。