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

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

ruisui882周前 (04-09)技术分析13

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

Linux世界的多样性:yum和apt的对比,让你感受不同发行版的特色

yum和apt是两种常用的Linux软件包管理器,它们都可以用来安装、更新和删除软件包。但是,它们之间也有一些重要的区别,本文将对它们进行分析对比。yum是Yellowdog Updater Modified的缩写,它是基于RPM(Red Hat Package Manager)的软件包管理器,主要...

代码管理-9-gitlab的使用和设置

gitlab使用1、外观设置完成后保存,返回登录页面查看关于注册,有些公司是不允许打开的,,有些人数非常多的公司就需要打开注册的功能,让人员自己注册,我们来给他特定的权限就可以,毕竟人非常多的时候还由我们来给她们注册就非常不现实了,工作量会很大2、自动注册3、组&用户&项目创建组设置组名称、描述等创...

30 个纯 HTML5 实现的游戏

浏览器和 JavaScript 的功能逐年不断的变强变大。曾几何时,任何类型的游戏都需要Flash。但随着 HTML5 发展,HTML5 + WebGL 游戏式就慢慢占领着这个舞台。以下是30款流行的游戏,它们可以在所有现代浏览器中运行,并且只使用web技术构建。1. HexGL地址:http://...

Python中的11 种数组算法

1. 创建数组 创建数组意味着留出一个连续的内存块来存储相同类型的元素。在大多数语言中,您可以在创建数组时指定数组的大小。假设您正在书架上整理一组书籍,并且您需要为正好 10 本书预留空间。功能架上的每个空间都对应于数组中的一个索引。# Example in Python arr = [1, 2,...

VUE 技术栈

官网链接:https://cn.vuejs.org/什么是vue:渐进式JavaScript 框架vue-cli链接:https://cli.vuejs.org/vue-cli安装:npm install -g @vue/clivue -V创建一个项目:vue create xxxxxx模版语法:文...

推荐一个Java微服务商业级Sass开源电商小程序(开源,企业级项目)

使用Java微服务开发,SpringBoot2框架、MyBatis-plus持久层框架、Redis作为缓存、MySql8作为数据库。 前端vuejs作为开发语言,使用uniapp编码,同时支持微信小程序、安卓App、苹果App。 支持集群部署,单机部署。 unimall 针对中小商户、企业和个人消...