这是我参与更文挑战的第 13 天,活动详情查看: 更文挑战
2021-06-13 Diff 前端造飞机
Diff算法(二),手写diff算法简单部分
1. 基本认识
首先要明白的是,前端框架里面的diff比较是在虚拟dom上的比较,因此第一节重点讲了VNode~
,接下来就具体说说diff的一些基本认识
1.1 diff基本规则
- 在vue中diff算法是最小量更新,因此key很重要,key是这个节点的唯一标识符,告诉diff算法,在更改前后他们是同一个DOM节点
- 只有是同样一个虚拟节点才会做精细化比较,否则就会暴力拆除旧的,插入新的;确认是否是同一个虚拟节点的基本方针是他们的选择器相同,且key相同
- 只进行同层比较,不会进行跨层比较。即使是同一片的虚拟节点,只要是跨层了,就不进行精细化比较,而是直接暴力拆除旧的,然后插入新的。
1.2 diff如何处理节点
如图:
2. 第一次上树处理
2.1 创建pacth函数
- 先要写一个
patch
函数
首先明确一个patch函数就是做diff算法的函数,作用就是新旧虚拟节点对比,然后虚拟节点变为真实的DOM节点,挂载到DOM树上去;
一般有3个策略,本期只讲第一种虚拟dom第一次上树和新旧对比不是同一节点暴力删除的情况
注意:h
函数和VNode
函数是采用上一期Diff算法(一),虚拟DOM
所写的函数这里不在罗列赘述
function patch(oldVNode,newVNode){
// 1. 判断传入的第一个参数,是dom节点还是虚拟节点
if(oldVNode.sel === '' || oldVNode.sel === undefined ){
// 说明是dom节点,要包装成虚拟节点
oldVNode = VNode( oldVNode.tagName.toLowerCase(),{},[],undefined,oldVNode )
}
console.log( oldVNode )
// 2. 判断oldVNode和newVNode是不是同一个节点
if(oldVNode.key === newVNode.key && oldVNode.sel === newVNode.sel){
// 同一个节点,精细化比较,内容较多下一期~!
return
}
// 3. 是不同的节点,暴力插入新的,拆除旧的
/**
* @function createEle 这是一个将虚拟节点变真实节点的函数
**/
const newDOM = createEle(newVNode);
oldVNode.elm.parentNode.insertBefore( newDOM ,oldVNode.elm);
// 删除旧节点
oldVNode.elm.parentNode.removeChild(oldVNode.elm)
}
复制代码
2.2 虚拟节点变为真实节点——createEle
在上面的patch函数里面我们可以很清楚的看到有一个createEle函数
将虚拟节点变真实节点的函数,那这个函数是怎么样的呢?其实很简单,其实就是js原生应用的能力,代码如下:
// 创建真实节点,作用将VNode创建为DOM
function createEle(VNode){
const domNode = document.createElement(VNode.sel);
if(VNode.text!=='' && ( VNode.children === undefined || VNode.children.length === 0)){
domNode.innerText = VNode.text;
}else if( Array.isArray(VNode.children) && VNode.children.length >0){
VNode.children.forEach( item =>{
const itemDOM = createEle(item)
domNode.appendChild(itemDOM);
})
}
// 因为创建生成为真实节点代表即将要上树的,前面说过,上树的时候虚拟节点的elm属性就是这个真实dom
VNode.elm = domNode;
return VNode.elm
}
复制代码
3. VNode上树精细化比较
首先看图,看完图之后,相信大家就有一了一个清晰的了解:
看完图之后,就对前面patch函数注释的精细化部分的处理是由如下几部分组成
- 比较新旧节点,是同一个对象不做处理,如果新节点没有children,只有text,则直接将旧的节点文本替换成新节点的文本,旧节点的子元素全部删除
- 比较新旧节点,若新节点有children,旧节点没有children,那么直接将新节点的children插入到就节点的children里面即可
我们先处理比较简单的第1,2点,给patch函数注释的精细化部分添加refineCompare(oldVNode,newVNode)
这个函数
// 同一个节点,精细化比较的各个处理函数
function refineCompare( oldVNode, newVNode){
if(oldVNode === newVNode){
return
}
if( newVNode.text !== undefined && ( newVNode.children === undefined || newVNode.children.length === 0)){
console.log('命中newVNode有text属性')
if(oldVNode.text !== newVNode.text){
oldVNode.elm.innerHtml = '';
oldVNode.elm.innerText = newVNode.text;
}
}else{
console.log('命中newVNode没有text属性')
// 判断老的有没有child
if(oldVNode.children && oldVNode.children.length >0 ){
// 老的有children,最复杂的情况,将面临最小量更新,难点,单独抽一节
console.log('最小量更新算法')
}else{
// 老的没有children,直接文本替换成newVNode的children即可
oldVNode.elm.innerText = ''
newVNode.children.forEach(item=>{
const dom = createEle(item);
oldVNode.elm.appendChild(dom);
})
}
}
}
复制代码
- 比较新旧节点,若新节点有children,旧节点也有chldren,那么就将进入最难的一部分,即最小量更新
到这里,我们基本上就对vue当中的虚拟节点如何变成真实节点并挂载到DOM树上的过程就有了一个大致的了解,其实只要看到这里并理解了VNode
,h函数
,patch函数
的作用,在背点diff的八股文,面试基本上不成问题了,在有限的时间内,面试官是难不住你的!欢迎点赞,关注下一期,diff算法(三),完结篇——最小量更新
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END