vue3.0 diff算法详解(超详细)

前言:随之vue3.0beta版本的发布,vue3.0正式版本相信不久就会与我们相遇。尤玉溪在直播中也说了vue3.0的新特性typescript强烈支持,proxy响应式原理,重新虚拟dom,优化diff算法性能提升等等。小编在这里仔细研究了vue3.0beta版本diff算法的源码,并希望把其中的细节和奥妙和大家一起分享。

在这里插入图片描述
首先我们来思考一些大中厂面试中,很容易问到的问题:

1 什么时候用到diff算法,diff算法作用域在哪里?
2 diff算法是怎么运作的,到底有什么作用?
3 在v-for 循环列表 key 的作用是什么
4 用索引index做key真的有用? 到底用什么做key才是最佳方案。

如果遇到这些问题,大家是怎么回答的呢?我相信当你读完这篇文章,这些问题也会迎刃而解。

一 什么时候用到了diff算法,diff算法作用域?

1.1diff算法的作用域

patch概念引入

在vue update过程中在遍历子代vnode的过程中,会用不同的patch方法来patch新老vnode,如果找到对应的 newVnode 和 oldVnode,就可以复用利用里面的真实dom节点。避免了重复创建元素带来的性能开销。毕竟浏览器创造真实的dom,操纵真实的dom,性能代价是昂贵的。

patch过程中,如果面对当前vnode存在有很多chidren的情况,那么需要分别遍历patch新的children Vnode和老的 children vnode。

存在chidren的vnode类型

首先思考一下什么类型的vnode会存在children。

①element元素类型vnode

第一中情况就是element类型vnode 会存在 children vode,此时的三个span标签就是chidren vnode情况

<div>
   <span> 苹果🍎 </span> 
   <span> 香蕉🍌 </span>
   <span> 鸭梨🍐 </span>
</div>

在vue3.0源码中 ,patchElement用于处理element类型的vnode

②flagment碎片类型vnode

在Vue3.0中,引入了一个fragment碎片概念。
你可能会问,什么是碎片?如果你创建一个Vue组件,那么它只能有一个根节点。

<template>
   <span> 苹果🍎 </span> 
   <span> 香蕉🍌 </span>
   <span> 鸭梨🍐 </span>
</template>

这样可能会报出警告,原因是代表任何Vue组件的Vue实例需要绑定到一个单一的DOM元素中。唯一可以创建一个具有多个DOM节点的组件的方法就是创建一个没有底层Vue实例的功能组件。

flagment出现就是用看起来像一个普通的DOM元素,但它是虚拟的,根本不会在DOM树中呈现。这样我们可以将组件功能绑定到一个单一的元素中,而不需要创建一个多余的DOM节点。

 <Fragment>
   <span> 苹果🍎 </span> 
   <span> 香蕉🍌 </span>
   <span> 鸭梨🍐 </span>
</Fragment>

在vue3.0源码中 ,processFragment用于处理Fragment类型的vnode

1.2 patchChildren

从上文中我们得知了存在children的vnode类型,那么存在children就需要patch每一个
children vnode依次向下遍历。那么就需要一个patchChildren方法,依次patch子类vnode。

patchChildren

vue3.0中 在patchChildren方法中有这么一段源码

if (patchFlag > 0) {
   
   
      if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
   
    
         /* 对于存在key的情况用于diff算法 */
        patchKeyedChildren(
          c1 as VNode[],
          c2 as VNodeArrayChildren,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
        return
      } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
   
   
         /* 对于不存在key的情况,直接patch  */
        patchUnkeyedChildren( 
          c1 as VNode[],
          c2 as VNodeArrayChildren,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
        return
      }
    }

patchChildren根据是否存在key进行真正的diff或者直接patch。

既然diff算法存在patchChildren方法中,而patchChildren方法用在Fragment类型和element类型的vnode中,这样也就解释了diff算法的作用域是什么。

1.3 diff算法作用?

通过前言我们知道,存在这children的情况的vnode,需要通过patchChildren遍历children依次进行patch操作,如果在patch期间,再发现存在vnode情况,那么会递归的方式依次向下patch,那么找到与新的vnode对应的vnode显的如此重要。

我们用两幅图来向大家展示vnode变化。

在这里插入图片描述
在这里插入图片描述
如上两幅图表示在一次更新中新老dom树变化情况。

假设不存在diff算法,依次按照先后顺序patch会发生什么

如果不存在diff算法,而是直接patchchildren 就会出现如下图的逻辑。

在这里插入图片描述

第一次patchChidren
在这里插入图片描述
第二次patchChidren
在这里插入图片描述
第三次patchChidren
在这里插入图片描述

第四次patchChidren

在这里插入图片描述
如果没有用到diff算法,而是依次patch虚拟dom树,那么如上稍微修改dom顺序,就会在patch过程中没有一对正确的新老vnode,所以老vnode的节点没有一个可以复用,这样就需要重新创造新的节点,浪费了性能开销,这显然不是我们需要的。

那么diff算法的作用就来了。

diff作用就是在patch子vnode过程中,找到与新vnode对应的老vnode,复用真实的dom节点,避免不必要的性能开销

二 diff算法具体做了什么(重点)?

在正式讲diff算法之前,在patchChildren的过程中,存在 patchKeyedChildren
patchUnkeyedChildren

patchKeyedChildren 是正式的开启diff的流程,那么patchUnkeyedChildren的作用是什么呢? 我们来看看针对没有key的情况patchUnkeyedChildren会做什么。


 c1 = c1 || EMPTY_ARR
    c2 = c2 || EMPTY_ARR
    const oldLength = c1.length
    const newLength = c2.length
    const commonLength = Math.
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值