彻底理解vue的patch流程和diff算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底理解vue的patch流程和diff算法相关的知识,希望对你有一定的参考价值。

参考技术A

上一篇 《vue异步更新流程梳理》 梳理了数据从赋值到更新到视图的整体流程。但是最后的步骤 vm._update(vm._render()) 只是粗略的提了一嘴,现在就仔细的研究它内部的细节,搞清楚patch流程和diff原理是我们看源码的重中之重。

当更新数据的时候就会执行这个updateComponent方法,即方法里面的 vm._update(vm._render()) ,vm.render() 得到一个vnode,那么vm._update到底干什么? 进去看看

至此,无论是初始化还是更新都是靠patch来完成的 ,我们只需要看update流程就可以了。进入patch内部

patch函数主要接收oldVnode 与 vnode两个参数,其实就是新旧两棵虚拟树。这里经过判断条件 !isRealElement && sameVnode(oldVnode, vnode),不是真实节点 且是相同的vnode,进入patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly); 我们只要关注oldVnode, vnode这两个参数即可。

按照我们的例子,此时的oldVnode 与 vnode分别是

此处只列出关键属性tag, key, elm, children,elm,还有很多其他的属性没有列出。真实的虚拟树节点应该是如下图

我们能看出 两个vnode之间就是children[0]的不同:

追踪流程发现,我们进入oldVnode 与 vnode的children进行对比,在updateChildren函数中。

我们先不去看updateChildren的逻辑,继续看patchVnode这个函数其他的逻辑分支,得出 oldVnode 与 vnode的对比流程

总结: patchVnode这个方法的主要作用是对比两个虚拟节点过程中去更新真实dom

接下来我们进入updateChildren流程,这是两个children的对比,看一下这个函数的定义

函数解读:

下面是两个数组进行diff的流程,也就是diff算法

diff解读:
新旧两个数组,都有双端指针,两端指针向中间靠拢,直到某个数组的两端指针相交则退出循环。

在这个过程中,会先判断是否有以下四种情况

如果不符合这4种情况,那就基于旧数组遍历一次,拿到每个节点的key和index,就是oldKeyToIdx: key1: 0, key2: 1这种情况。然后去新数组首个节点开始匹配,匹配到就进行递归patchVnode流程,没匹配到就进行创建新节点,插入到真实dom节点里面去。

当循环结束,此时要么是旧数组相交,要么是新数组相交,只有这两种情况:

至此diff流程结束。

两个虚拟树进行对比:
patch(oldVnode, vnode) -> patchVnode(oldVnode, vnode) -> updataChildren(oldCh, newCh)
在updataChildren(oldCh, newCh)的过程中也会进行 patchVnode(oldVnode, vnode) ,如此虚拟树深度优先递归diff完成。

更加详细直观的图看此链接
https://www.processon.com/view/5e809004e4b08e4e2447d02e

vue中diff算法处理新旧节点的流程

vue中diff算法处理新旧节点的流程

patch函数的作用

function patch(oldVnode: VNode | Element, vnode: VNode): VNode 
    let i: number, elm: Node, parent: Node;
    const insertedVnodeQueue: VNodeQueue = [];
    for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();

    //是虚拟节点还是dom节点,如果是dom节点包装成虚拟节点
    if (!isVnode(oldVnode)) 
      oldVnode = emptyNodeAt(oldVnode);
    
    // sameVnode函数比较是否是同一个节点
    if (sameVnode(oldVnode, vnode)) 
      patchVnode(oldVnode, vnode, insertedVnodeQueue);
     else 
      elm = oldVnode.elm!;
      parent = api.parentNode(elm) as Node;
      createElm(vnode, insertedVnodeQueue);
      <!-- 不是同一个节点,暴力拆除 -->
      if (parent !== null) 
        api.insertBefore(parent, vnode.elm!, api.nextSibling(elm));
        removeVnodes(parent, [oldVnode], 0, 0);
      
    
    for (i = 0; i < insertedVnodeQueue.length; ++i) 
      insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i]);
    
    for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
    return vnode;
  ;

patch函数的作用有两点:
1.是虚拟节点还是dom节点,如果是dom节点包装成虚拟节点
2.调用sameVnode函数比较是否是同一个节点。在比较他们的子代
  不是同一个节点,暴力拆除,新节点替换旧节点

sameVnode的比较

通过sameVnode函数去做的处理。
如果新的虚拟节点和旧的虚拟节点的key值相同,并且他们的选择器(tag标签)相同
说明是同一个虚拟节点。这一句话也是sameVnode函数的工作原理

function sameVnode(vnode1: VNode, vnode2: VNode): boolean 
  <!-- 新旧节点的key值相同 -->
  const isSameKey = vnode1.key === vnode2.key;
  <!-- 选择器(tag标签)相同 -->
  const isSameSel = vnode1.sel === vnode2.sel;
  return isSameSel && isSameKey ;

diff算法处理新旧节点的流程

1.patch函数被调用:是虚拟节点还是dom节点,如果是dom节点包装成虚拟节点。
2.调用sameVnode函数比较是否是同一个节点。如果是在比较他们的子代,
  如果不是暴力拆除,新节点替换旧节点

sameVnode的原理:如果新的虚拟节点和旧的虚拟节点的key值相同,并且他们的选择器(tag标签)相同
说明是同一个虚拟节点。这一句话也是sameVnode函数的工作原理
作者:明月人倚楼
出处:https://www.cnblogs.com/IwishIcould/

想问问题,打赏了卑微的博主,求求你备注一下的扣扣或者微信;这样我好联系你;(っ•̀ω•́)っ✎⁾⁾!

如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,或者关注博主,在此感谢!

万水千山总是情,打赏5毛买辣条行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主(っ•̀ω•́)っ✎⁾⁾!

想问问题,打赏了卑微的博主,求求你备注一下的扣扣或者微信;这样我好联系你;(っ•̀ω•́)っ✎⁾⁾!

支付宝
微信
本文版权归作者所有,欢迎转载,未经作者同意须保留此段声明,在文章页面明显位置给出原文连接
如果文中有什么错误,欢迎指出。以免更多的人被误导。

以上是关于彻底理解vue的patch流程和diff算法的主要内容,如果未能解决你的问题,请参考以下文章

Snabbdom:虚拟DOM和Diff算法

React入门学习-- diffing 算法

React入门学习-- diffing 算法

Vuejs351- 带你解析vue2.0的diff算法

vue和react的diff算法的区别

Vue原理-虚拟DOM和diff算法