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

现在我们能够通过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函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue2和Vue3都是流行的前端框架,它们在虚拟DOMdiff算法上有一些区别。下面我会详细介绍一下Vue2和Vue3的diff算法Vue2的diff算法Vue2使用的是经典的双指针算法来进行虚拟DOMdiff过程。大致的步骤如下: 1. 创建新旧虚拟DOM树(VNode),并进行比较。 2. 对新旧虚拟DOM树进行同层级的节点对比,找出差异。 3. 如果两个节点类型不同,则直接替换整个节点及其子节点。 4. 如果两个节点类型相同,则进行更详细的比较。 5. 对于有key的节点,通过key来匹配新旧节点,减少移动节点的操作。 6. 对于没有key的节点,使用遍历的方式进行比较,效率较低。 7. 如果在旧节点集合中找不到匹配的节点,则认为是新增节点,创建并插入到正确的位置。 8. 如果在新节点集合中找不到匹配的节点,则认为是删除节点,从DOM中移除。 Vue2的diff算法存在一些缺点: 1. 每次更新都需要对整个VNode树进行遍历,效率较低。 2. 对于没有key的节点,会使用遍历的方式进行比较,导致性能下降。 3. 当VNode树较大时,diff算法的性能会受到影响。 Vue3的diff算法Vue3采用了一种更高效的diff算法,称为静态标记和提升(Static Markup and Hoisting)。它的主要思想是通过编译阶段的静态分析,将动态节点和静态节点进行标记,从而减少diff的过程。 Vue3的diff算法具体步骤如下: 1. 在编译阶段,通过静态分析将模板中的动态节点和静态节点进行标记。 2. 对于静态节点,会将其提升为常量,并在patch过程中跳过对这些节点的比较。 3. 对于动态节点,会使用类似Vue2的diff算法进行比较和更新。 4. 对于列表渲染(v-for)的情况,会通过唯一的key来进行精确匹配和复用节点。 5. 通过静态标记和提升,减少了不必要的比较和更新操作,提高了diff算法的效率。 Vue3的diff算法相比Vue2有以下优点: 1. 在编译阶段进行静态标记和提升,减少了运行时的工作量。 2. 可以更精确地识别出动态节点和静态节点,减少不必要的比较和更新操作。 3. 对于列表渲染,通过唯一的key进行精确匹配和复用节点,提高了性能。 总结: Vue2和Vue3的diff算法都是基于虚拟DOM的思想,但Vue3引入了静态标记和提升的概念,通过编译阶段的静态分析来优化diff过程,提高了性能。在实际开发中,如果需要更高的性能,推荐使用Vue3。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值