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

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

ruisui883周前 (04-09)技术分析15

网上看了一些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算法” 的相关文章

vue3中父子组件之间传值的详解

首先我们回顾一下vue2中父子组件是怎么传值的,然后对比vue3进行详解。一、vue2中父子组件传值<!-- 父组件 --> <template> <div> // name:父组件把值传给子组件test-child // childFn:...

「 VUE3 + TS + Vite 」父子组件间如何通信?

组件之间传值,大家都很熟悉,涉及到 VUE3 +TS 好多同学就无从下手了,所以分享这篇文章,希望看完后提起 VUE3+TS 能够不慌不忙。平时使用的函数如:ref、reactive、watch、computed 等需要先引入才能使用,但是本篇文章介绍的 defineProps、withDefaul...

抖音 Android 性能优化系列:启动优化实践

启动性能是 APP 使用体验的门面,启动过程耗时较长很可能使用户削减使用 APP 的兴趣,抖音通过对启动性能做劣化实验也验证了其对于业务指标有显著影响。抖音有数亿的日活,启动耗时几百毫秒的增长就可能带来成千上万用户的留存缩减,因此,启动性能的优化成为了抖音 Android 基础技术团队在体验优化方向...

USB电池充电基础:应急指南

USB为便携设备供电与其串行通信功能一样,已经成为一种标准应用。如今,USB 供电已经扩展到电池充电、交流适配器及其它供电形式的应用。应用的普及带来的一个显著效果是便携设备的充电和供电可以互换插头和适配器。因此,相对于过去每种装置都采用专用适配器的架构相比,目前的解决方案允许采用多种电源进行充电。毋...

vue3使用vue-router路由(路由懒加载、路由传参)

vue-router 是 vue的一个插件库1. 专门用来实现一个SPA单页面应用2 .基于vue的项目基本都会用到此库SPA的理解1) 单页Web应用(single page web application,SPA)2) 整个应用只有一个完整的页面3) 点击页面中的链接不会刷新页面, 本身也不会向...

Vue真是太好了 壹万多字的Vue知识点 超详细!

1??、Vue和其他两大框架的区别Angular 学习成本太高React 代码可读性差Vue 学习成本较低 很容易上手VUE官方: https://cn.vuejs.org/v2/guide/comparison.html?2??、Vue是什么Vue是一套用于构建用户界面的渐进式框架 "前端...