【vue设计与实现】简单Diff算法 2-DOM复用与Key的作用

之前通过减少DOM操作的次数,提升了性能,但是还是有可优化的空间,例如,假设新旧两组子节点的内容如下:

// oldChildren
[
	{type:'p'},
	{type:'div'},
	{type:'span'}
]

// newChildren
[
	{type:'span'},
	{type:'p'},
	{type:'div'}
]

使用之前的算法来的话,需要6次DOM操作,因为类型不同,需要卸载旧节点后再加载新节点,这样每更新一个节点就需要两次DOM操作。

但是通过观察可以发现,两组节点只是顺序不一样,所以最优的处理方式是,通过DOM的移动来完成子节点的更新。但是想要通过DOM的移动来完成更新,必须要保证一个前提,新旧两组子节点中的确存在可复用的节点。那么,应该如何确定新的子节点是否出现在就的一组子节点中,有一种方案是通过vnode.type来判断,只要vnode.type的值相同,就认为是相同的节点,但是这样不靠谱,如果vnode相同,而children不同呢。那么可以引入额外的key来作为vnode的标识,如下面代码:

// oldChildren
[
	{type:'p',children:'1',key:1},
	{type:'p',children:'2',key:2},
	{type:'p',children:'3',key:3},
]

// newChildren
[
	{type:'p',children:'3',key:3},
	{type:'p',children:'1',key:1},
	{type:'p',children:'2',key:2},
]

这样只要两个虚拟节点的type属性值和key属性值都相同,那么就认为它们是相同的,即可以进行DOM复用。

当然DOM可复用并不意味这不需要更新,如下面两个虚拟节点:

const oldVnode = {type:'p',key:1,children:'text 1'}
const oldVnode = {type:'p',key:1,children:'text 2'}

这意味着,在更新时可以复用DOM元素,仍然需要对这两个虚拟节点进行打补丁操作。因此,在讨论如何移动DOM之前,需要先完成打补丁的操作,如下面patchChildren函数的代码所示:

function patchChildren(n1,n2,container){
	if(typeof n2.children === 'string'){
		// 省略部分代码
	}else if(Array.isArray(n2.children)){
		const oldChildren = n1.children
		const newChildren = n2.children
		
		// 遍历新的children
		for(let i=0;i<newChildren.length;i++){
			const newVNode = newChildren[i]
			// 遍历旧的children
			for(let j = 0; j<oldChildre.length; j++){
				const oldVNode = oldChildren[j]
				// 如果找到了具有相同key值的两个节点,说明可以复用,但仍然需要调用patch函数进行更新
				if(newVNode.key === oldVNode.key){
					patch(oldVNode,newVNode,container)
					break
				}
			}
		}
		
	}else{
		/.../
	}
}

这样就能保证所有可复用的节点本身都已经更新完毕,以下面的新旧两组子节点为例:

const oldVnode = {
	type: 'div',
	children: [
		{type:'p',children:'1',key:1},
		{type:'p',children:'2',key:2},
		{type:'p',children:'3',key:3},
	]
}

const newVnode = {
	type: 'div',
	children: [
		{type:'p',children:'world',key:3},
		{type:'p',children:'1',key:1},
		{type:'p',children:'2',key:2},
	]
}

// 首次挂载
renderer.render(oldVnode,document.querySelector('#app'))
setTimeout(()=>{
	// 1秒后更新
	renderer.render(newVnode, document.querySelector('#app'))
},1000)

经过上面的更新操作后,所有节点对应的真实DOM元素都更新完毕了。但真实DOM仍然保持就的一组子节点的顺序,因此还需要通过移动节点来完成真实DOM顺序的更新

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值