【vue设计与实现】简单Diff算法 3-找到需要移动的元素 & 移动元素

这篇博客讨论了在虚拟DOM更新过程中,如何判断和处理节点的移动。通过记录节点索引,确定需要移动的节点,并使用insertBefore方法实现DOM元素的移动。文章还强调了DOM复用的重要性,并提供了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现在我们能够通过key值找到可复用的节点,但是如何判断一个节点是否需要移动,以及如何移动。对于第一个问题,可以先这么想:在什么情况下节点不需要移动?其实很简单,当新旧两组子节点的节点顺序不变时,就不需要额外的移动操作
更新算法在进行时,每一次寻找可复用的节点时,都会记录该可复用节点在旧的一组子节点的位置索引
如果发现旧的子节点和新的子节点位置索引不同,则节点是需要移动的。其实可以将索引值最大的节点在旧children中的索引定义为:在旧children中寻找具有相同key中,遇到的最大索引值如果在后续寻找的过程中,存在索引值比当前遇到的最大索引值还要小的节点,则意味着该节点需要移动

可以用lastIndex变量存储整个寻找过程中遇到的最大索引值,代码如下:

function patchChildren(n1,n2,container){
	if(typeof n2.children === 'string'){
		// ...
	}else if(Array.isArray(n2.children){
		const oldChildren = n1.children
		const newChildren = n2.children

		// 用来存储寻找过程中遇到的最大索引值
		let lastIndex = 0
		for(let i=0;i<newChildren.length;i++){
			const newVNode = newChildren[i]
			for(let j=0;j<oldChildren.length;j++){
				const oldVNode = oldChildren[j]
				if(newVNode.key === oldVNode.key){
					patch(oldVNode,newVNode,container)
					if(j<lastIndex){
						// 如果当前找到的节点在旧children中的索引小于最大索引值lastIndex
						// 说明该节点对应的真实DOM需要移动
						// lastIndex从0开始
					}else{
						// 如果当前找到的节点在旧children中的索引不小于最大索引值lastIndex
						// 则更新lastIndex的值
						lastIndex = j
					}
					break
				}
			}
		}
	}
	
}

如何移动元素

移动节点指的是,移动一个虚拟节点所对应的真实DOM节点。注意移动的是真实节点,要移动真实DOM节点,就需要取得对其的引用才行。而当虚拟节点被挂载后,其对应的真实DOM节点会存储在vnode.el属性中。因此在代码中可以通过旧子节点的vnode.el属性来取得对应的真实DOM节点。

而在patchElement函数中首先就讲旧节点的n1.el属性赋值给新节点的n2.el属性。
const el = n2.el = n1.el
这个赋值语句的真正含义其实就是DOM元素的复用。在这之后,新节点也有对真实DOM的引用
其实新children的顺序其实就是更新后真实DOM节点应有的顺序

这样就可以开始着手实现代码,如下所示:

function patchChildren(n1,n2,container){
	if(typeof n2.children === 'string'){
		// ...
	}else if(Array.isArray(n2.children){
		const oldChildren = n1.children
		const newChildren = n2.children

		// 用来存储寻找过程中遇到的最大索引值
		let lastIndex = 0
		for(let i=0;i<newChildren.length;i++){
			const newVNode = newChildren[i]
			for(let j=0;j<oldChildren.length;j++){
				const oldVNode = oldChildren[j]
				if(newVNode.key === oldVNode.key){
					patch(oldVNode,newVNode,container)
					if(j<lastIndex){
						// 先获取newVNode的前一个vnode,即preVNode
						const preVNode = newChildren[i-1]
						// 如果preVNode不存在,则说明当前newVNode是第一个节点,不需要移动
						if(preVNode){
							// 由于要移动到preVNode对应真实DOM后面
							// 所以需要获取preVNode所对应真实DOM的下一个兄弟节点,并将其作为锚点
							const anchor = preVNode.el.nextSibling
							// 调用insert方法将newVNode对应的真实DOM插入到锚点元素前面
							// 也就是prevVNode对应真实DOM的后面
							insert(newVNode.el, container, anchor)
						}
					}else{
						// 如果当前找到的节点在旧children中的索引不小于最大索引值lastIndex
						// 则更新lastIndex的值
						lastIndex = j
					}
					break
				}
			}
		}
	}
	
}

要注意的是这里insert函数依赖浏览器原生的insertBefore函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值