9.3 找到需要移动的元素
用
lastIndex记录旧 children 中已匹配节点的最大下标,凡是匹配到更小下标的节点,就是需要移动的节点。
一、思路:维护 lastIndex
遍历新 children,为每个节点在旧 children 中找到相同 key 的节点(下标为 j):
lastIndex表示「寻找过程中遇到的最大索引值」(旧 children 里已匹配节点的最大下标);- 只要出现
j < lastIndex,说明该节点在新序列中跑到了前面,其对应的真实 DOM 需要移动; - 否则
lastIndex = j,把最大值更新。
// 用来存储寻找过程中遇到的最大索引值
let lastIndex = 0
// 遍历新的 children
for (let i = 0; i < newChildren.length; i++) {
const newVNode = newChildren[i]
// 遍历旧的 children
for (let j = 0; j < oldChildren.length; j++) {
const oldVNode = oldChildren[j]
if (newVNode.key === oldVNode.key) {
patch(oldVNode, newVNode, container)
if (j < lastIndex) {
// 索引比最大索引值还小 → 该节点对应的真实 DOM 需要移动
console.log('move') // 本节只做标记,下一节才真正移动
} else {
// 索引不小于最大索引值 → 更新 lastIndex
lastIndex = j
}
break // 找到即复用,必须 break
}
}
}
二、推演:old [p1, p2, p3] → new [p3, p1, p2]
| i | newVNode | 在旧 children 中的下标 j | j < lastIndex | 更新后 lastIndex | 结论 |
|---|---|---|---|---|---|
| 0 | p3 | 2 | 否(2 ≥ 0) | 2 | 不需要移动 |
| 1 | p1 | 0 | 是(0 < 2) | 2 | 需要移动 |
| 2 | p2 | 1 | 是(1 < 2) | 2 | 需要移动 |
lastIndex只增不减,代表「已经确认排在新序列靠后位置的最大旧下标」。后续节点若用了更小的旧下标,就说明它要插到前面去,因此必须移动。
三、本节只做判断
这一版只把需要移动的节点标记出来(console.log('move')),不执行任何 DOM 操作;真正的移动在 9.4 中完成。
相关
- 9.2 DOM 复用与 key 的作用:用 key 找出可复用节点
- 9.4 如何移动元素:用 insertBefore 把标记出的节点移动到正确位置