当前位置:首页 > 技术分析 > 正文内容

高级前端开发带你搞懂vue的diff算法

ruisui882个月前 (04-09)技术分析27

网上看了一些diff的算法,但是感觉看完之后,还是那么的一知半解,为什么一个简单的diff算法,不能直接画个流程图就简单的明了了呢,说动就动,下面的是本人基于vue版本2.6.11源码为各位读友进行的解析

Vue的diff流程图

流程前说明

  1. 由于diff的过程是对vnode(虚拟dom)树进行层级比较,所以以同一层级作为例子
  2. 下面将旧节点列表的起始和终止节点称为OS(OldStarVnode)和OE(OldEndVnode),用index标志遍历过程OS和OE的变化。即OS和OE的index称为OSIndex和OEIndex。同理得新节点的为NS和NE,NSIndex和NEIndex,如下图

主流程

如下图:

文字版描述一下就是:

  1. 判断是否遍历完,未遍历则开始2,否则,如果遍历完了旧节点列表,则未遍历的新节点则创建并且增加到节点列表,如果遍历完了新节点列表,则未遍历的旧节点在节点列表里面删除
  2. 对旧节点的OS和OE进行判空,如果为空,则跳过该节点,继续从1开始;否则继续3
  3. 对OS,OE,NS,NE进行两两比较,如果相等,则更新节点并且指针向下一个移动,继续从1开始;否则继续4
  4. 判断NS是否有key,有key则判断NS是否在旧节点列表里面找到key一样的进行更新;否则创建NS并且插入节点列表

updateChildren进行diff算法源码

  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by 
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(newCh)
    }

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newendidx if isundefoldstartvnode oldstartvnode='oldCh[++oldStartIdx]' vnode has been moved left else if isundefoldendvnode oldendvnode='oldCh[--oldEndIdx]' else if samevnodeoldstartvnode newstartvnode patchvnodeoldstartvnode newstartvnode insertedvnodequeue newch newstartidx oldstartvnode='oldCh[++oldStartIdx]' newstartvnode='newCh[++newStartIdx]' else if samevnodeoldendvnode newendvnode patchvnodeoldendvnode newendvnode insertedvnodequeue newch newendidx oldendvnode='oldCh[--oldEndIdx]' newendvnode='newCh[--newEndIdx]' else if samevnodeoldstartvnode newendvnode vnode moved right patchvnodeoldstartvnode newendvnode insertedvnodequeue newch newendidx canmove nodeops.insertbeforeparentelm oldstartvnode.elm nodeops.nextsiblingoldendvnode.elm oldstartvnode='oldCh[++oldStartIdx]' newendvnode='newCh[--newEndIdx]' else if samevnodeoldendvnode newstartvnode vnode moved left patchvnodeoldendvnode newstartvnode insertedvnodequeue newch newstartidx canmove nodeops.insertbeforeparentelm oldendvnode.elm oldstartvnode.elm oldendvnode='oldCh[--oldEndIdx]' newstartvnode='newCh[++newStartIdx]' else if isundefoldkeytoidx oldkeytoidx='createKeyToOldIdx(oldCh,' oldstartidx oldendidx idxinold='isDef(newStartVnode.key)' oldkeytoidxnewstartvnode.key : findidxinoldnewstartvnode oldch oldstartidx oldendidx if isundefidxinold new element createelmnewstartvnode insertedvnodequeue parentelm oldstartvnode.elm false newch newstartidx else vnodetomove='oldCh[idxInOld]' if samevnodevnodetomove newstartvnode patchvnodevnodetomove newstartvnode insertedvnodequeue newch newstartidx oldchidxinold='undefined' canmove nodeops.insertbeforeparentelm vnodetomove.elm oldstartvnode.elm else same key but different element. treat as new element createelmnewstartvnode insertedvnodequeue parentelm oldstartvnode.elm false newch newstartidx newstartvnode='newCh[++newStartIdx]' if oldstartidx> oldEndIdx) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }

附,源码中部分工具函数的解释:

isUndef 对节点进行判空

function isUndef (v) {
  return v === undefined || v === null
}

sameVnode对节点进行判断是否相等

  1. 判断新旧节点的key
  2. 判断新旧节点的属性(tag,isComment表示是否是注释节点,isDef表示是否为非空节点,sameInputType表示是否同个Input节点)是否一致
  3. 判断新旧节点的加载函数asyncFactory是否一致
function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

patchVnode更新节点

patchVnode更新节点主要做以下事情,代码比较长就不贴了,影响读者,需要可以直接阅读源码:

  1. 判断vnode和oldvnode是否相等,相等直接返回
  2. 处理静态节点的情况
  3. 对vnode如果是可patch的情形进行调用update
  4. 对vnode进行判断是否是根节点(即文本节点),如果是,则进行5,否则则对其子节点进行遍历更新
  5. 判断vnode和oldvnode文本是否一样: 不一样则替换节点文本

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/3393.html

标签: vue 流程图
分享给朋友:

“高级前端开发带你搞懂vue的diff算法” 的相关文章

Acustica Audio 发布模拟Roland Jupiter 双声道合成器插件 TH2

福利: Acustica Audio 发布模拟Roland Jupiter 风格的双声道合成器插件 TH2 免费下载 意大利 Acustica Audio 公司发布布模拟Roland Jupiter 风格的双声道合成器插件 TH2 ,灵感来源于Acustica Audio的THING-8系列,它是...

你感动了吗?佳能超规格镜头 RF 24-105mm F2.8深度测评

如果要你选一支用作多题材创作的挂机镜头,那我相信很多人会选择24-105mm这个焦段的镜头。作为一支可以实现从广角到长焦的变焦镜头,24-105mm有着丰富的焦段选择。只是基于镜头体积以及光学结构上的限制,此前的24-105mm镜头只能恒定在F4的光圈。而佳能打破了这一限制,将实用焦段和恒定光圈完美...

js中数组filter方法的使用和实现

定义filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。语法var newArray = arr.filter(callback(element[, index[, selfArr]])[, thisArg])参数callback循环数组每个元素时调用的回调函数。回调函...

三勾商城(java+vue3)微信小程序商城+SAAS+前后端源码

项目介绍本系统功能包括: 前台展示+后台管理+SAAS管理端,包括最基本的用户登录注册,下单, 购物车,购买,结算,订单查询,收货地址,后台商品管 理,订单管理,用户管理等等功能,小伙伴一起来看看吧。三勾小程序商城基于springboot+element-ui+uniapp打造的面向开发的小程序商城...

什么是同步通信?什么是异步通信?它们有什么区别?

串行通信一般又分为同步和异步通信,同步通信需要同步时钟信号,而异步通信则不需要同步时钟信号。同步通信:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。异步通信:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。同步通信与异步通信有什么区别呢?1、同步通信要求接收端...

深入理解同步/异步与阻塞/非阻塞区别

编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由「那谁」投稿。转载请注明来自高可用架构公众号「ArchNotes」。「那谁」,codedump.info 博主,多年从事互联网服务器后台开发工作。几年前曾写过一篇描写同步/异步以及阻塞/非阻塞的文章,最近再回头看,还存在一些理解和认知误...