kd-cloud-web / Blog

一群人, 关于前端, 做一些有趣的事儿
13 stars 1 forks source link

#关于diff算法 #39

Open doreenChenD opened 4 years ago

doreenChenD commented 4 years ago

1.从虚拟dom开始讲起 2.关于diff 3.vue的diff

从虚拟dom开始讲起

什么是虚拟dom? 我们知道真实的dom操作很消耗性能, 主要有2个方面原因: (1)访问dom

(2)修改dom引起重绘重排

我们知道真实的dom元素,每一个都有天然带有很多属性,导致数据体积庞大。 dom 在操作真实dom的同时,往往引起大量的重绘重排,需要很大的性能损耗。所以我们希望尽量减少dom操作。相对于渲染的操作,js代码的运行效率可谓很高效。所以程序员们尝试使用JS,通过对象的数据结构去模拟DOM数据,在进行真实的DOM操作之前,先进行一系列优化判断优化模拟的DOM数据,最小化需要改变的真实dom数,大大优化了性能。 这就是虚拟dom,是真实dom的一个映射。 image

上面的代码用html表示为 image

我们知道vue以及react都采用了虚拟dom来优化效率。那么他们是如何一系列操作,以确保需要修改操作的真实dom最小呢?这个过程用的就是diff算法。

关于diff算法

diff这个概念,我的理解就是找出2个对比物之间的不同。diff的概念其实很早就出现了。

vue的diff

流程图

image


image 在初始化vue时,vue生成了一个expOrFn是Function,且function里包含了_update的操作的watcher,在watcher初始化时,会将type为function的expOrFn赋值给watcher的getter,最后将在watcher调用update的时候别调用。

-Watcher.get 调用getter image

  1. set方法会让闭包中的Dep调用notify
  2. dep通知所有订阅者Watcher,调用watcher.update
  3. Watcher => watcher.update => watcher.run => watcher.getAndInvoke => watcher.get => watcher => getter 执行vm._update(vm._render(), hydrating)。

update方法的逻辑很简单,第一个参数是一个VNode对象,在内部会将该VNode对象与之前旧的VNode对象进行patch

patch源码

image

  1. 如果vnode不存在,但是oldVnode存在,说明是需要销毁旧节点,则调用invokeDestroyHook(oldVnode)来销毁oldVnode。
  2. 如果vnode存在,但是oldVnode不存在,说明是需要创建新节点,则调用createElm来创建新节点。
  3. 当vnode和oldVnode都存在时如果vnode与oldVnode 非真实元素且为同一个节点时,进行调用patchVnode方法。
  1. 如果oldVnode和vnode完全一致,则可认为没有变化,return;
  2. 如果oldVnode跟vnode都是静态节点(实例不会发生变化),且具有相同的key,并且当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上,也不用再有其他操作,return;
  3. 否则,如果vnode不是文本节点或注释节点
  4. 如果vnode和oldVnode都有子节点并且两者的子节点不一致时,就调用updateChildren更新子节点
  5. 如果只有vnode有子节点,则调用addVnodes创建子节点
  6. 如果只有oldVnode有子节点,则调用removeVnodes把这些子节点都删除
  7. 如果vnode文本为undefined,则清空vnode.elm文本;
  8. 如果vnode是文本节点但是vnode.text != oldVnode.text时只需要更新vnode.elm的文本内容就可以。

下面我们通过图片来分析过程: 初始化参数 image

节点对比 image 都不匹配与key处理 image 余量处理 image

从上面过程我们可以看出diff的过程的比较范围一个元素的一级子元素,diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。 同颜色的框表示需要使用diff算法进行比较 image image

过程: