前言
我们都知道,浏览器的DOM操作是十分昂贵、十分浪费性能的!
Vue通过虚拟DOM的方式优化了这部分性能浪费,它的核心原理是通过diff算法对比新老节点之间的差异,判断哪些节点可以复用,减少DOM操作的浪费,提升性能!
所以,diff算法的本质就是--找出两个vnode之间的差异,尽可能复用节点!
为何采用虚拟DOM
vue的编写者其实给出了自己的答案:
1、为函数式的UI编程方式打开了大门;
2、可以渲染到DOM以外的backend。
针对这两点谈谈自己的理解:
1、为函数式的UI编程方式打开了大门;
每次生成新的ui就需要重新刷新页面代价太过昂贵,虚拟DOM以及diff算法的引入可以最大限度的复用旧的DOM,使得渲染性能大幅提升。
2、可以渲染到DOM以外的backend。
有了虚拟DOM就可以轻松实现跨平台,多平台的core都相同,只是在render到具体平台的时候采取不同的render就好了
前面介绍了diff算法的本质就是比较vnode的异同,那么vnode都包含哪些属性呢?
其实仔细思考下,一个dom节点主要包含三个部分:
<div id='node'>
<p id='diff'>哈哈</p>
</div>
1、自身的标签名(div)
2、自身的属性(id='node')
3、子节点(p)
所以我们可以设计如下的对象结构表示一个dom节点
const oldVnode = {
tag:'div',
attrs:{id:'node'},
children:[{ tag:'p',attrs:{id:'diff'},children:['哈哈']}]
}
当用户对界面进行操作,比如把div的id改为dom ,将子节点span的文本子节点‘哈哈’改为‘嘻嘻’,那么我们可以得到如下vnode
const newVnode = {
tag:'div',
attrs:{id:'dom'},
children:[{ tag:'p',attrs:{id:'diff'},children:['嘻嘻']}]
}
那么我们运行diff(oldVnode,newVnode),就能知道oldVnode和newVnode之间的差异如下: div的id改为dom span的文本子‘哈哈’改为‘嘻嘻 知道了差异部分,我们就能更新视图,伪代码如下:
document.getElementById("app").setAttribute('id', 'app2')// id 改为 app2
document.getElementById("child").firstChild.textContent ='2' //1 改为 2
上面整个过程就是整个diff过程的预演,真实的diff过程跟上面整个过程大同小异,只是需要考虑更多的边界条件!下面让我们来了解一下真正的diff算法
diff算法执行的时机
在了解diff算法的执行时机之前,我们先简单看一下vue框架的渲染过程,如下图所示:
从图中可以看出,当我们更新data中响应式数据的值时,vue的patch()函数执行过程中会通过diff算法对比新老vnode的异同,然后更新vnode树,当对比完成以后会统一将有变更的虚拟接点渲染成前端页面。
diff算法
diff算法包括二部分:
1、vnode树的遍历
2、节点对比,只对比同层的节点
vnode树的遍历:
虚拟DOM说到底只是一颗树形结构,对于树的遍历我们知道有深度遍历和广度遍历
目前,不管是vue还是react,采用的都是深度遍历算法
深度遍历需要栈结构,可以通过递归(内核维护调用栈)的方式实现,也可以采用人为构造栈,然后循环栈完成深度遍历。通常深度优先搜索法不全部保留结点,扩展完的结点从栈中弹出删去,这样,在栈中存储的结点数就是深度值,因此它占用空间较少。
节点对比:
对于相同的节点,继续比较子节点:
同一级子元素新老虚拟DOM列表分别设置startIndex和endIndex,首先,旧首和新首对比,旧尾和新尾对比,然后交叉判断startIndex和endIndex是否是相同元素
对比的结果有三种情况:相同、新增、删除、移位
相同
保持不变
新增
老的startIndex不动,新的startIndex移位,并在老的startIndex元素前插入
移位:
新startIndex和老startIndex或者新endIndex和老endIndex相同,只要移动startIndex或者endIndex就可以了;
新startIndex和老endIndex相同,新startIndex++,老endIndex--,将老endIndex的ele插入到老startIndex的ele前面
新endIndex和老startIndex相同,新endIndex--,老startIndex++,将老的startIndex的ele插入到老endIndex的ele的后面
新startIndex的key匹配到老的vnode的key,将老vnode的ele插入到老startIndex的ele前面,还有一个操作:将老vnode标记位undefined,(oldCh[idxInOld] = undefined)
删除
等新startIndex和新endIndex合拢,老startIndex和老endIndex之间的非undefined的vnode的ele全部删除,undefined的node代表已经处理过了(移位)
举例说明
1、oldVnode与newVnode头指针指向的节点相同,DOM节点保持不变,oldVnode与newVnode头指针分别向后前一位。
2、oldVnode与newVnode尾指针指向的节点相同,DOM节点保持不变,oldVnode与newVnode尾指针分别向后退一位。
3、newVnode尾指针节点与oldVnode头指针节点是同一个节点,将oldVnode头指针对应的DOM插入到oldVnode尾指针对应的DOM之后,newVnode尾指针向后退一位,oldVnode头指针向前移一位。
(4)oldVnode包含newVnode头指针节点,将newVnode尾指针对应的DOM插入到oldVnode头指针对应的DOM之前,oldCh头指针向前移一位,newCh尾指针向后退一位。
5、newVnode不包含oldVnode头指针节点,将oldVnode头指针对应的DOM删除,newCh头指针向前移一位,oldCh尾指针向后退一位。
6、oldVnode不包含newVnode头指针节点,将newVnode头指针节点插入到oldVnode头指针对应的DOM之前,newCh头指针向前移一位。
7、newVnode已经没了,oldVnode剩余的节点4、6、7说明都被删除了,删除对应dom即可。
8、最终,DOM完全更新为与newNode一样的结构,diff过程完毕!
参考文献
1、www.jianshu.com/p/081103a62…
2、juejin.cn/post/684490…
3、www.jianshu.com/p/211c7f216…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!