何为diff算法
vue跟react都是基于虚拟dom的,当产生新的vDom时,需要跟之前的dom进行比对,判断dom需要修改的部分,再批量的进行更新。这里,对比两个vdom树的过程,也称之为diff算法。
vue2 diff算法
vue2的diff算法是采用的双端diff算法,采用的策略是同层比较、深度优先
头尾的指针向中间移动,直到oldStartIndex > newStartIndex 或者 newStartIndex大于 newEndIndex,每次对比两端的节点是否可以复用,如果可以复用,则直接移动,如果没有找到,则会去旧数组里去找,如果找到了就移动,如果没找到就创建一个。
核心代码
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVNode.key === newStartVNode.key) { // 头头
patch(oldStartVNode, newStartVNode, container)
oldStartVNode = oldChildren[++oldStartIdx]
newStartVNode = newChildren[++newStartIdx]
} else if (oldEndVNode.key === newEndVNode.key) {//尾尾
patch(oldEndVNode, newEndVNode, container)
oldEndVNode = oldChildren[--oldEndIdx]
newEndVNode = newChildren[--newEndIdx]
} else if (oldStartVNode.key === newEndVNode.key) {//头尾,需要移动
patch(oldStartVNode, newEndVNode, container)
insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling)
oldStartVNode = oldChildren[++oldStartIdx]
newEndVNode = newChildren[--newEndIdx]
} else if (oldEndVNode.key === newStartVNode.key) {//尾头,需要移动
patch(oldEndVNode, newStartVNode, container)
insert(oldEndVNode.el, container, oldStartVNode.el)
oldEndVNode = oldChildren[--oldEndIdx]
newStartVNode = newChildren[++newStartIdx]
} else {
// 头尾没有找到可复用的节点
}
}
// 头尾没有找到可复用的节点 去旧节点查找
const idxInOld = oldChildren.findIndex(
node => node.key === newStartVNode.key
)
if (idxInOld > 0) {
const vnodeToMove = oldChildren[idxInOld]
patch(vnodeToMove, newStartVNode, container)
insert(vnodeToMove.el, container, oldStartVNode.el)
oldChildren[idxInOld] = undefined
} else {
patch(null, newStartVNode, container, oldStartVNode.el)
}
还需要补充 某一个节点数组查找完之后的逻辑
if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
// 添加新节点
for (let i = newStartIdx; i <= newEndIdx; i++) {
patch(null, newChildren[i], container, oldStartVNode.el)
}
} else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
// 移除操作
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
unmount(oldChildren[i])
}
}
节点是如何对比判断能否复用的
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
)
)
}
为什么不建议用index做key
当我们在index做key的数组里去删除节点的时候,通过sameNode方法获得相同的节点,但是内容缺不相同,本来是可以通过移动元素就可以解决问题,但是实际却会删除节点再重新创建。
vue3 diff算法
vue3的diff算法是基于最长递增子序列来实现的,首先我们了解一下最长递增子序列。
这里就需要引入一个leetcode经典算法题最长递增子序列,
动态规划解法
动态规划的思想就是假设,需要一个dp数组,我们假设dp[0] - dp[i-1]都可以计算出来,我们怎么根据已有的,计算出结果dp[i],dp[i]在这里表示以nums[i]结尾的最长递增子序列的长度。
核心逻辑
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j])
dp[i] = Math.max(dp[i], dp[j] + 1);
}
进行遍历,当后加入的一个节点比当前节点大时,进行取大操作。
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
// base case:dp 数组全都初始化为 1
Arrays.fill(dp, 1);
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j])
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
int res = 0;
for (int i = 0; i < dp.length; i++) {
res = Math.max(res, dp[i]);
}
return res;
}
vue3动态算法核心代码
- 第一步从头到尾进行寻找
/* c1 老的vnode c2 新的vnode */
let i = 0 /* 记录索引 */
const l2 = c2.length /* 新vnode的数量 */
let e1 = c1.length - 1 /* 老vnode 最后一个节点的索引 */
let e2 = l2 - 1 /* 新节点最后一个节点的索引 */
/* 从头对比找到有相同的节点 patch ,发现不同,立即跳出*/
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
/* 判断key ,type是否相等 */
if (isSameVNodeType(n1, n2)) {
patch(
n1,
n2,
container,
parentAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
break
}
i++
}
isSameVNodeType
// 判断key跟vnode类型是否相同
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
return n1.type === n2.type && n1.key === n2.key
}
- 从尾开始同前diff
/* 如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环 */
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = (c2[e2] = optimized
? cloneIfMounted(c2[e2] as VNode)
: normalizeVNode(c2[e2]))
if (isSameVNodeType(n1, n2)) {
patch(
n1,
n2,
container,
parentAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
break
}
e1--
e2--
}
3.如果老节点是否全部patch,新节点没有被patch完,创建新的vnode
4.如果新节点全部被patch,老节点有剩余,那么卸载所有老节点
/* 如果新的节点大于老的节点数 ,对于剩下的节点全部以新的vnode处理( 这种情况说明已经patch完相同的vnode ) */
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
while (i <= e2) {
patch( /* 创建新的节点*/
null,
(c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i])),
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
i++
}
}
} else if (i > e2) {
while (i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true)
i++
}
}