CatalogVue.js1.1 patch 流程1.2 updateChildren 大概步骤1.3 updateChildren 代码实现1.4 两端比对 DEMOReact.js2.1 updateChildren 大概步骤2.2 u
Catalog
-
Vue.js
1.1 patch 流程
1.2 updateChildren 大概步骤
1.3 updateChildren 代码实现
1.4 两端比对 DEMO
-
React.js
2.1 updateChildren 大概步骤
2.2 updateChildren 代码实现
2.3 节点移动、新增 、移除 DEMO
-
Reference
Vue.js Diff 算法
patch 流程
- oldVNode 判断 是否存在
- oldVNode 存在
- VNode 判断 是否存在
- VNode 存在
- patch
- sameVnode 判断 key && tag && isComment && isDef(a.data) && sameInputType(a, b) 基本属性是否相同
- sameVnode 相同
- diff
- VNode 判断 是否是文本节点
- 是,若 oldVNode.text !== VNode.text 则直接进行文本节点替换
- 否,进入子节点 diff
- oldCh 不存在,cn 存在,清空 oldVNode 的文本节点,同时调用 addVnodes 方法将 cn 添加到 elm 真实的 dom 节点中
- oldCh 节点存在,cn 不存在,则删除 elm 真实节点下的 oldCn 子节点
- oldCh 存在,cn 存在,调用 updateChildren 对子节点进行 diff
- oldVNode 有文本节点,vnode 没有,那么就清空这个文本节点
- sameVnode 不相同
- 删除老的 dom 节点,生成新的 dom
- VNode 不存在
- 移除 DOM
- oldVNode 不存在
- 挂载 DOM
updateChildren 大概步骤
-
建立两组首尾索引和节点变量
-
当满足 oldStartIdx <= oldEndIdx 并且 newStartIdx <= newEndIdx 条件时进入循环
-
双端比较 => 循环中先进行比较找出 key 相同的新旧节点,根据四种情况判断是否移动 DOM 和增减首尾索引变量以及 patch
-
有 key 时 => 当双端匹配没有找到 key 相同的节点,通过当前 newStartVNode 的 key 在 nextChildren 中寻找 key 相同的 VNode
-
key 相同的 VNode 存在时,对当前节点进行移动 DOM、patch 并将旧 children 中该位置的节点设为 undefined,将 newStartIdx 下移一位
-
key 相同的 VNode 不存在,则当前“第一个”节点为新节点,将其挂载到 oldStartVNode 之前,将 newStartIdx 下移一位
-
挂载全新的节点
-
当 oldEndIdx < oldStartIdx, 添加新节点
-
移除不存在的节点
-
当 newEndIdx < newStartIdx, 移除不存在的元素
updateChildren 代码实现
let oldStartIdx = 0;
let oldEndIdx = prevChildren.length - 1;
let newStartIdx = 0;
let newEndIdx = nextChildren.length - 1;
let oldStartVNode = prevChildren[oldStartIdx];
let oldEndVNode = prevChildren[oldEndIdx];
let newStartVNode = nextChildren[newStartIdx];
let newEndVNode = nextChildren[newEndIdx];
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVNode.key === newStartVNode.key) {
...
} else if (oldEndVNode.key === newEndVNode.key) {
...
} else if (oldStartVNode.key === newEndVNode.key) {
...
} else if (oldEndVNode.key === newStartVNode.key) {
...
}else {
}
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (!oldStartVNode) {
oldStartVNode = prevChildren[++oldStartIdx]
} else if (!oldEndVNode) {
oldEndVNode = prevChildren[--oldEndIdx]
} else if (oldStartVNode.key === newStartVNode.key) {
patch(oldStartVNode, newStartVNode, container)
oldStartVNode = prevChildren[++oldStartIdx]
newStartVNode = nextChildren[++newStartIdx]
} else if (oldEndVNode.key === newEndVNode.key) {
patch(oldEndVNode, newEndVNode, container)
oldEndVNode = prevChildren[--oldEndIdx]
newEndVNode = nextChildren[--newEndIdx]
} else if (oldStartVNode.key === newEndVNode.key) {
patch(oldStartVNode, newEndVNode, container)
container.insertBefore(
oldStartVNode.el,
oldEndVNode.el.nextSibling
)
oldStartVNode = prevChildren[++oldStartIdx]
newEndVNode = nextChildren[--newEndIdx]
} else if (oldEndVNode.key === newStartVNode.key) {
patch(oldEndVNode, newStartVNode, container)
container.insertBefore(oldEndVNode.el, oldStartVNode.el)
oldEndVNode = prevChildren[--oldEndIdx]
newStartVNode = nextChildren[++newStartIdx]
} else {
const idxInOld = prevChildren.findIndex(
node => node.key === newStartVNode.key
)
if (idxInOld >= 0) {
const vnodeToMove = prevChildren[idxInOld]
patch(vnodeToMove, newStartVNode, container)
container.insertBefore(vnodeToMove.el, oldStartVNode.el)
prevChildren[idxInOld] = undefined
} else {
mount(newStartVNode, container, false, oldStartVNode.el)
}
newStartVNode = nextChildren[++newStartIdx]
}
}
if (oldEndIdx < oldStartIdx) {
for (let i = newStartIdx; i <= newEndIdx; i++) {
mount(nextChildren[i], container, false, oldStartVNode.el)
}
} else if (newEndIdx < newStartIdx) {
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
container.removeChild(prevChildren[i].el)
}
}
两端比对 DEMO
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { diff... }
VNode
|
startIndex/endIndex
|
a b c d
|
0 / 3
|
d b a c
|
0 / 3
|
next
|
next
|
a b c x
|
0 / 2
|
x b a c
|
1 / 3
|
next
|
next
|
a b x x
|
0 / 0
|
x b a x
|
2 / 2
|
next
|
next
|
a x x x
|
1 / 0
|
x x a x
|
1 / 0
|
next
|
next
|
x x x x
|
0 / 0
|
x x x x
|
0 / 0
|
complete
|
complete
|
两端比对 - 添加新元素 DEMO
VNode
|
startIndex/endIndex
|
a b
|
0 / 1
|
a d b
|
0 / 2
|
next
|
next
|
A b
|
1 / 1
|
A d b
|
1 / 2
|
next
|
next
|
A B
|
1 / 0
|
A d B
|
1 / 1
|
next
|
next
|
A d B
|
1 / 0
|
A d B
|
1 / 1
|
complete
|
complete
|
两端比对 - 添加新元素 DEMO2
VNode
|
startIndex/endIndex
|
a b c
|
0 / 2
|
d a b c
|
0 / 3
|
next
|
next
|
a b C
|
0 / 1
|
d a b C
|
0 / 2
|
next
|
next
|
a B C
|
0 / 0
|
d a B C
|
0 / 1
|
next
|
next
|
A B C
|
0 / -1
|
d A B C
|
0 / 0
|
next
|
next
|
d A B C
|
0 / -1
|
d A B C
|
0 / 0
|
next
|
next
|
complete
|
complete
|
两端比对 - 移除不存在的元素 DEMO
VNode
|
startIndex/endIndex
|
a b c
|
0 / 2
|
a c
|
0 / 1
|
next
|
next
|
A b c
|
1 / 2
|
A c
|
1 / 1
|
next
|
next
|
A b C
|
1 / 1
|
A C
|
1 / 0
|
next
|
next
|
A C
|
1 / 1
|
A C
|
1 / 0
|
next
|
next
|
complete
|
complete
|
React.js Diff 算法
updateChildren 大概步骤
-
初始化最大索引值变量(lastIndex)为 0
-
遍历新节点数组,初始化是否匹配标识符(find)为 false
-
循环旧节点数组,查找 key 相同的节点
-
存在,先进行 patch,后判断节点是否需要移动(j<lastIndex)
-
需要移动=>移动 DOM
-
不需要移动=>更新 lastIndex 变量为当前索引,连接新节点到对应 DOM 上
-
不存在,则为新节点,需要挂载 DOM 到上一个新节点的后面
-
处理已经不存在的节点
-
遍历旧节点数组,移除新节点数组中不存在的节点
updateChildren 代码实现
let lastIndex = 0;
for (let i = 0; i < nextChildren.length; i++) {
const nextVNode = nextChildren[i];
let j = 0,
find = false;
for (j; j < prevChildren.length; j++) {
const prevVNode = prevChildren[j];
if (nextVNode.key === prevVNode.key) {
find = true;
patch(prevVNode, nextVNode, container);
if (j < lastIndex) {
const refNode = nextChildren[i - 1].el.nextSibling;
container.insertBefore(prevVNode.el, refNode);
} else {
lastIndex = j;
const el = (nextVNode.el = prevVNode.el);
}
break;
}
if (!find) {
const refNode =
i - 1 < 0 ? prevChildren[0].el : nextChildren[i - 1].el.nextSibling;
mount(nextVNode, container, false, refNode);
}
}
for (let i = 0; i < prevChildren.length; i++) {
const prevVNode = prevChildren[i];
const has = nextChildren.find(
(nextVNode) => nextVNode.key === prevVNode.key
);
if (!has) {
container.removeChild(prevVNode.el);
}
}
}
节点移动、新增、移除 DEMO
VNode
|
lastIndex
|
是否需要移动 DOM
|
是否新增 DOM
|
是否移除 DOM
|
a b d
|
/
|
/
|
/
|
/
|
a d c
|
0
|
n
|
n
|
n
|
next
|
d
|
next
|
next
|
next
|
a b d
|
/
|
/
|
/
|
/
|
a d c
|
2
|
n
|
n
|
n
|
next
|
c
|
next
|
next
|
next
|
a b d
|
/
|
/
|
/
|
/
|
a d c
|
2
|
n
|
y
|
n
|
next
|
移除旧 DOM
|
next
|
next
|
next
|
a b d c
|
/
|
/
|
/
|
y
|
a d c
|
/
|
/
|
/
|
/
|
next
|
next
|
next
|
next
|
next
|
a b c
|
/
|
/
|
/
|
/
|
a d c
|
/
|
/
|
/
|
/
|
complete
|
complete
|
complete
|
complete
|
complete
|
Reference