胖蔡叨叨叨
你听我说

Vue2的patch流程和diff算法

Vue通过虚拟DOM实现对页面渲染的监听更新,降低对真实DOM的操作。而patch则是Vue虚拟DOM实现的基石,它能快速实现虚拟DOM的对比更新,并最终将vnode渲染成真实DOM。整个patch的过程就是:创建节点、删除节点和修改节点的过程。

创建节点

当因状态改变而新增的节点在DOM中并不存在时,我们需要创建一个节点并插入到DOM中。即当oldVnode不存在而vnode存在时,就需要使用vnode生成真实的DOM元素并将其插入到视图当中。

1、首次渲染时,DOM中不存在任何节点,即oldVnode是不存在的,我们需要使用vnode创建一个新DOM节点并渲染视图。

2、当vnode oldVnode完全不是同一个节点时,即oldVnode是一个被废弃的节点,vnode是一个全新的节点,此时,我们需要使用vnode创建一个新DOM节点,用它去替换oldVnode所对应的真实DOM节点。

vnode是有类型的,所以当我们在创建节点时,最重要的是根据vnode的类型来创建出相同类型的DOM元素。事实上,只有三种类型的节点会被创建并插入到DOM中:元素节点注释节点文本节点

删除节点

因为渲染视图时,需以vnode为标准,所以vnode中不存在的节点都属于被废弃的节点,需要从DOM中删除。当vnode oldVnode完全不是同一个节点时,在DOM中需要使用vnode创建新节点替换oldVnode所对应的旧节点,而替换的过程就是将新创建的DOM节点插入到旧节点的旁边,然后再将旧节点删除。

在要删除节点的父元素上调用removeChild方法即可。

更新节点

无论是新增节点,还是删除节点,他们之间都有一个共同点,那就是两个虚拟节点是完全不同的。而当新旧两个节点是相同的节点时,我们需要对这两个节点进行比较细致的比对,然后对oldVnode在视图中所对应的真实节点进行更新。

更新节点需要对以下3种情况进行判断并分别处理:

  • 如果VNodeoldVNode均为静态节点:无需处理
  • 如果VNode是文本节点:如果VNode是文本节点即表示这个节点内只包含纯文本,那么只需看oldVNode是否也是文本节点,如果是,那就比较两个文本是否不同,如果不同则把oldVNode里的文本改成跟VNode的文本一样。如果oldVNode不是文本节点,那么不论它是什么,直接调用setTextNode方法把它改成文本节点,并且文本内容跟VNode相同。
  • 如果VNode是元素节点:如果VNode是元素节点,则又细分两种情况
    • 该节点不包含子节点
    • 该节点包含子节点

patch流程

1、当oldVnode不存在时,直接使用vnode渲染视图;

2、当oldVnodevnode都存在但并不是同一个节点时,使用vnode创建的DOM元素替换旧的DOM元素;

3、当oldVnodevnode是同一个节点时,使用更详细的对比操作对真实的DOM节点进行更新。

patch过程中最关键的是运营Vue-Diff算法,完成新旧vnode的精细化对比。通过Diff算法,我们可以计算出虚拟DOM中被改变的部分,然后针对该部分进行原生DOM操作,而不用重新渲染整个页面,从而提升性能。

原始diff算法

原始diff算法就是,两个虚拟DOM树,进行不分层级的逐一比对,也就是说,一个虚拟DOM树,从根节点到以后分支的每一个节点,都要单独拿出来跟新生成的节点做比较,这就是最原始的diff算法。

这个diff算法的时间复杂度表面上看是(n ^2),因为单独一个个的去跟另外的n个相比较,肯定是n ^2次就比较结束了,但是实际上不是的,比较完之后还要计算如何在最优的地方放置最佳的节点,所以就是O(n ^3)了。


虽然原始的Diff算法从功能上解决了先对比再处理实际DOM的需求,但是实际上我们的流程变得更加的复杂和笨拙。

优化Diff算法

优化Diff算法只比较同一层级 ,不做跨级比较。因为在实际的web展示中,非同级的节点移动是非常少的,所以可以选择做同级比较。

所谓同级比较,即只比较同层的节点,不同层不做比较。不同层的只需要删除原节点,并且新建插入更新节点。

Vue-Diff算法,采用的就是优化过的Diff算法,同层比较,不会跨级,且其比较是从两侧向中间进行的,这种方式相对于从左到右依次比对的方式来说,更高效。

Vue-diff 策略

1、Tree Diff

Tree Diff是对树每一层进行遍历,找出不同。

2、Component Diff

Vue是基于组件构建的,对于组件间的比较采用的策略如下:

  • 如果是同一类型的组件,则按照原策略比较组件的虚拟 DOM 树,否则不需要比较。
  • 如果是不同类型的组件,则将该组件判断为dirty component,从而替换整个组件下的所有子节点。

3、Element Diff

在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这就是Element DiffElement Diff时,提供了3种节点操作,分别为INSERT_MARKUP(插入),MOVE_EXISTING(移动),REMOVE_NODE(删除)。

  • INSERT_MARKUP:新的组件类型不在旧集合中,即全新的节点,需要对新节点进行插入操作。
  • MOVE_EXISTING:旧集合中有新组件类型,且element是可更新的类型,这时候就需要做移动操作,可以复用以前的DOM节点。
  • REMOVE_NODE:旧组件类型,在新集合里也有,但对应的element不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作。

Vue-diff过程

定义4个指针:OldStartIdx、OldEndIdx、NewStartIdx、NewEndIdx。比较的是两个指针对应的节点的虚拟DOM是否为同一个。

具体步骤如下:

  1. 比较OldStartIdxNewStartIdx
  2. 如果两个startIdx相同,则两个指针都会+1,也就是向后移一位,重新生成OldStartIdxNewStartIdx指针。
  3. 如果两个startIdx不一致,则比较两个endIdx
  4. 比较OldEndIdxNewEndIdx
  5. 如果两个endIdx一致,则两个endIdx都减1,也就是向前移一位,再执行步骤1。
  6. 如果两个startIdx和两个endIdx都不一致,则比较捺向的oldStartIdxNewEndIdx
  7. 如果oldStartIdxNewEndIdx一致,则把oldStartIdx指向的虚拟DOM里的真实DOM节点,挪到OldEndIdx位置之后,oldStartIdx加1向后移一位,newEndIdx减1向前移动一位。
  8. 如果竖向和捺向都不一致,则比较撇向oldEndIdxNewStartIdx
  9. 如果撇向一致,则把oldEndIdx指向的真实dom节点挪到oldStartIdx所在的真实dom前,同时oldEndIdx减1向前移动一位,newStartIdx加1向后移动一位。
  10. 如果竖向、捺向、撇向都不一致,则看有没有key。
  11. 如果有key,就能快速找到,并挪到oldStartIdx前。
  12. 如果没有key,就遍历oldStartIdxoldEndIdx之间的所有节点,寻找newStartIdx指向的节点是否存在于这些老的vdom中。如果有,就把它挪到oldStartIdx前;没有就在oldStartIdx之前创建一个节点,newEndIdx减1向前移动一位。这样比较下去,一直到newEndIdx<newStartIdx

newEndIdx<newStartIdx,这时new vnode生成完毕,然后将old vnode中多余的部分删掉即可,也就是oldStartIdxoldEndIdx指向的dom及中间的部分。


赞(0) 打赏
转载请附上原文出处链接:胖蔡叨叨叨 » Vue2的patch流程和diff算法
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏