在之前的生命周期中有提到过虚拟dom的相关概念,这里来简单介绍一个vue的diff算法的原理

1.virtual dom

无论是vue还是react,在更新渲染的过程中,都是先根据真实dom生成一个虚拟dom对象,如图下所示


当某个节点发生变化时,vue都会根据新的vnode与oldVnode进行对比,如果不一样,就会直接修改在真实dom树。
这里要说一点,对于简单的dom结构,有时候直接修改真实dom的效率要比加一层virtual dom效率摇号,但是在项目庞大复杂的背景下,virtual dom拥有绝对的优势,它避免了很多没必要的操作。夸张的举个例子,比方说在一颗有上万个子节点且多层的dom树上,一旦一个子节点发生了改变,除非我们直接获取该节点(但是实际情况上我们做不到给每一个子节点添加标记,例如id和ref等等),那这个时候,在virtual dom上操作显然是方便许多,避免了很多在真实dom上遍历的操作。因此,其实virtual dom在很多并不是一个最优解,但是它绝对是最普适的。

2.diff算法

既然实质是比较两个对象的异同,我们关心的是如何进行比较,下图就很好解释了比较的过程,同层但不跨层(react和vue的共同之处)

举一个具体的例子

按照从上到下的比较过程,其实我们发现在第二层的时候,就已经有些不同了,这里其实就很容易看出来,我们只要把移到

后面就可以完成更新了。但是记住,比较的过程是同层不能跨层,所以diff操作在第2层会移除掉 ,并在第3层的时候,添加一个。
在vue中,diff算法的实质其实是调用了patch函数,一边比较一边打补丁,哪里不一样补哪里。


我们很容易的看出patch函数有两个参数oldVnode和vnode,也就是旧新节点,我们来看一下Vnode这个对象
el所对应的就是真实dom节点,但是在vnode对象里,el的值为null,因为其还并没有对应的真实dom。

现在来解析一下patch函数


逻辑很明确,如果新旧节点相等(注意这里指的是key和sel相等,也就是类型(例如p和span)和类(样式),这和react中的逻辑不同),那他俩就值得比较,所以我们就执行patchVnode函数,以此来比较他们的子节点是否相同(逐层)

1.const el = vnode.el = oldVnode.el ,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。
2.if(oldVnode ===vnode),如果她俩引用一致,那就直接return掉
3.如果它俩的text不同,就更新text
4.否则,调用updateEle(el,vnode,oldVnode),更新el节点
下面三步就是
1.如果它俩都有子节点且它不同,那我们就要执行updateChildren(),更新他们的子节点
2.如果vnode有,OldVnode没有,则将Vnode的子节点真实化之后添加到el
3.如果OldVnode有,vnode没有,则删除OldVnode的子节点。

2.如果不一样,则执行

我们先保存OldVnode的父节点(真实dom),然后createEl(vnode),为vnode创建他的真实dom,也就是vnode.el = 真实dom。然后就将新真实dom插入,移除旧真实dom。
最后,return vnode,就将有真实dom的vnode返回,其在下一次就会被当做oldVnode了。

这样就简单介绍完了,感谢
https://segmentfault.com/a/1190000008782928
https://www.cnblogs.com/wind-lanyan/p/9061684.html

等一下!!!!!!!!!!!!!!!!
是不是还有啥没讲,对!没错就是updateChildren,diff算法的精髓所在,在之前我只是把他一笔带过,是因为代码太长且难以一时理解(即便是在看别人的解析下),所以我决定自己去研读,逐行解读,这里也希望大家能够自己去看代码当看不懂解析的时候,用自己的方式去理解。(大可不必去看里面写的注释),这边其实就是给大家一个源码自己去阅读。
(这里偷个图,形象的理解一下)

updateChildren (parentElm, oldCh, newCh) {   //传入的值分别是,el,oldChidren,newChildrenlet oldStartIdx = 0, newStartIdx = 0  //设定旧和新的头索引(可以理解为指针)let oldEndIdx = oldCh.length - 1       //设定oldChildren的尾索引,指向最后一个let oldStartVnode = oldCh[0]          //设定old开始节点为oldCh[0]let oldEndVnode = oldCh[oldEndIdx]     //设定old结束节点为oldCh[oldEndIdx]let newEndIdx = newCh.length - 1       //同理,同上,newCh也这么操作let newStartVnode = newCh[0]           let newEndVnode = newCh[newEndIdx]let oldKeyToIdx                        //设定oldkey值let idxInOld                       let elmToMovelet before                                             while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {//如果两个头索引都是小于尾索引if (oldStartVnode == null) {   // 头节点=null,则++oldStartVnode = oldCh[++oldStartIdx]  }else if (oldEndVnode == null) {   //尾节点为null,则--oldEndVnode = oldCh[--oldEndIdx]}else if (newStartVnode == null) {        //头节点为null,则++newStartVnode = newCh[++newStartIdx]}else if (newEndVnode == null) {        //尾节点为null,则--newEndVnode = newCh[--newEndIdx]  ------------------------------sameVnode分割线          }else if (sameVnode(oldStartVnode, newStartVnode)) {//如果old和new的两个头结点一样patchVnode(oldStartVnode, newStartVnode)//则将两个头结点带入patchVnode中执行oldStartVnode = oldCh[++oldStartIdx]          //头索引++newStartVnode = newCh[++newStartIdx]            //头索引++}else if (sameVnode(oldEndVnode, newEndVnode)) {   //如果old和new两个尾节点一样patchVnode(oldEndVnode, newEndVnode)//则将两个尾节点带入patchVnode中执行oldEndVnode = oldCh[--oldEndIdx]                //尾索引--newEndVnode = newCh[--newEndIdx]             //尾索引--}else if (sameVnode(oldStartVnode, newEndVnode)) {//若old头结点===new尾节点patchVnode(oldStartVnode, newEndVnode)//则将头结点和尾节点带入patchVnode中执行//由于是新的尾节点和旧的头节点一样,我们就把真实dom中的第一个节点(oldStartVnode.el)//移到最后api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))oldStartVnode = oldCh[++oldStartIdx]   //旧头++newEndVnode = newCh[--newEndIdx]       //新尾--}else if (sameVnode(oldEndVnode, newStartVnode)) {//若旧尾节点===新头节点patchVnode(oldEndVnode, newStartVnode)//则将旧尾和新头带入patchVnode中执行//在头和尾的操作中,我们要明确一个逻辑,就是旧要跟着新的来//所以这里就是把旧尾所对应的真实dom移到旧头的前面。api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)oldEndVnode = oldCh[--oldEndIdx]      //旧尾--newStartVnode = newCh[++newStartIdx]   //新头++}else {if (oldKeyToIdx === undefined) {oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表}idxInOld = oldKeyToIdx[newStartVnode.key]if (!idxInOld) {api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)newStartVnode = newCh[++newStartIdx]}else {elmToMove = oldCh[idxInOld]if (elmToMove.sel !== newStartVnode.sel) {api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)}else {patchVnode(elmToMove, newStartVnode)oldCh[idxInOld] = nullapi.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)}newStartVnode = newCh[++newStartIdx]}}}//上述代码,其实就是在移动位置,目的都是改变oldCh中所对应的真实dom的位置,使其对应//newCh的位置,那么,位置确定了,接下来要改的就是打补丁了,多的删掉,少的补上。if (oldStartIdx > oldEndIdx) { //如果旧头大于旧尾(说明oldCh长度是小于等于NewCh),//那么我们需要增加真实dom节点before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].eladdVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)}else if (newStartIdx > newEndIdx) {//反之则删除真实dom节点removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)}
}

2.vue的diff算法(2020.12.07)相关推荐

  1. 2.react的diff算法(2020.12.07)

    正常情况下,比较两个树形结构的差异的算法的时间复杂度为O(n^3),这个效率显然无法直接接收的,react通过总结DOM的实际使用场景提出了两个在绝大多数实践场景下都成立的假设,基于这两个假设,rea ...

  2. [vue] 你了解vue的diff算法吗?

    [vue] 你了解vue的diff算法吗? 我的理解:计算出虚拟 DOM 中真正变化的部分,并且只针对该部分进行 DOM 更新,而非重新渲染整个页面 个人简介 我是歌谣,欢迎和大家一起交流前后端知识. ...

  3. Vue中diff算法的理解

    Vue中diff算法的理解 diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DOM结 ...

  4. vue的diff算法原理

    1. 为什么要用Diff算法 由于在浏览器中操作DOM是很昂贵的,频繁的操作DOM,会产生一定的性能问题,这就是虚拟DOM的产生原因.虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象状 ...

  5. [Vue源码] Vue中diff算法原理

    一. Vue中diff算法原理 理解: 1.先同级比较,在比较子节点 2.先判断一方有儿子一方没儿子的情况 3.比较都有儿子的情况 4.递归比较子节点 图: 1.原节点:ABCD,新节点:ABCDE, ...

  6. 详解vue的diff算法

    前言 目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,一起来get吧. 先来了解几个点... 1. 当数据发生变化时,vue是怎么更新节点的? 要知道渲染真实 ...

  7. vue的diff 算法

    1. 当数据发生变化时,vue是怎么更新节点的? 要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修 ...

  8. Vue之diff算法

    前言 Vue通过双向绑定来实现数据驱动视图更新,当数据更新会触发Dep对象notify通知所有的订阅者Watcher对象,Watcher对象最后会调用run来执行Watcher对象的getter方法. ...

  9. 【vue】diff 算法详解

    一.diff算法是什么 diff算法是一种通过同层的树节点进行比较的高效算法 diff算法的目的就是找出新旧不同虚拟DOM之间的差异,使最小化的更新视图,所以 diff 算法本质上就是比较两个js对象 ...

最新文章

  1. CMake结合PCL库学习(1)
  2. Tomcat学习总结(2)——Tomcat使用详解
  3. C# winform 编写记事本
  4. xml.etree ElementTree简介
  5. php使用常量cont,php常量介绍
  6. 什么是hibernate懒加载?什么时候用懒加载?为什么要用懒加载?
  7. 纪念硕士论文圆满答辩结束——20180614
  8. linux下组态软件,linux组态软件入门使用
  9. 实战之8051驱动8位数码管
  10. oracle查看登录失败次数,Oracle取消用户连续登录失败次数限制
  11. python 分词 词性_分词及词性标注
  12. 如何面试大厂web前端?(沟通软技能总结)
  13. java项目编码问题解决
  14. python terminal 库_zhihu-terminal 终端版知乎客户端
  15. 这是一个全民销售的时代
  16. 【经济学视频课程】科斯定理的本质…
  17. git删除目录下的所有文件并提交
  18. 第一课:句子成分与基本句型
  19. 【NOIP2006PJ】开心的金明(happy)
  20. 2011年10月大盘下跌大股东增持股票

热门文章

  1. Android Room 之存储 Objects 中的 List
  2. linux精灵进程之crond
  3. centos 7 vs centos6 的不同
  4. android实战开发02
  5. UIImagePickerController选择图片发送后旋转90度的问题
  6. python培训多久能入职_Python学到什么程度可以面试工作?
  7. 【工程项目经验】mac电脑lldb调试工具
  8. sublime后缀_提高数据分析工作效率-Sublime如何设置默认打开文件格式
  9. 编写一个计算机程序用来计算一个文件的 16 位效验和(Java实现)
  10. javaweb(04) xml