热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Vue3.0diff算法及原理

ue3.0采取的diff算法和2.0的双端比较有点不同。大概的原理如下c1:ab[cde]fgc2:ab[dech]fg复制代码假如有如上的c1和c2新旧children&

ue 3.0 采取的 diff 算法和 2.0 的双端比较有点不同。大概的原理如下

// c1: a b [ c d e ] f g
// c2: a b [ d e c h ] f g

假如有如上的 c1 和 c2 新旧 children,在 diff 的时候,会有一个预处理的过程。

先从前往后比较,当节点不同时,不再往后进行比较。接着又从后往前进行比较,当节点不同时,不再往前进行比较。

经过预处理之后,c1 和 c2 真正需要进行 diff 的部分如下所示:

// c1: c d e
// c2: d e c h

最后利用 “最长递增子序列”,完成上述差异部分的比较,提高 diff 效率。


处理相同的前后节点

预处理过程代码如下所示:

const patchKeyedChildren = (c1,c2,container,parentAnchor,parentComponent,parentSuspense,isSVG,optimized
) => {let i = 0; const l2 = c2.lengthlet e1 = c1.length - 1let e2 = c2.length - 1// 1. sync from startwhile (i <= e1 && i <= e2) {const n1 = c1[i]const n2 = c2[i]if (isSameVNodeType(n1, n2)) {patch(n1,n2,container,parentAnchor,parentComponent,parentSuspense,isSVG,optimized)} else {break}i++}// 2. sync from endwhile (i <= e1 && i <= e2) {const n1 = c1[e1]const n2 = c2[e2]if (isSameVNodeType(n1, n2)) {patch(n1,n2,container,parentAnchor,parentComponent,parentSuspense,isSVG,optimized)} else {break}e1--e2--}
}

仅有节点新增或移除

进行预处理还有一个好处,就是在某些情况下,我们可以明确的知道有节点的新增或者删除。


  • 节点新增

i、e1、e2 满足下述关系时,可以认为是有节点新增

// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) {if (i <= e2) {const nextPos = e2 + 1;const anchor = nextPos } else if {//
} else {//
}

  • 节点移除

i、e1、e2 满足下述关系时,可以认为是有节点被移除

// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
if (i > e1) {//
} else if (i > e2) {while (i <= e1) {unmount(c1[i], parentComponent, parentSuspense, true)i++}
} else {//
}

有节点移动、新增或删除

有时候情况可能没有上述那么地简单,即 i、e1、e2 并不满足上述两种情形时,我们就要寻找其中需要被移除、新增的节点,又或是判断哪些节点需要进行移动。

为此,我们需要去遍历 c1 中还没有进行处理的节点,然后查看在 c2 中是否有对应的节点(key 相同)。没有,则说明该节点已经被移除,那就执行 unmount 操作。

首先,为了快速确认 c1 的节点在 c2 中是否有对应的节点及所在的位置,对 c2 中的节点建立一个映射 (key: index)

// 5. unknown sequence
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]: a b [d e c h] f g
// i = 2, e1 = 4, e2 = 5
if (i > e1) {//
} else if (i > e2) {//
} else {const s1 = iconst s2 = iconst keyTOnewIndexMap= new Map()// 5.1 build key:index map for newChildrenfor (i = s2; i <= e2; i++) {const nextChild = c2[i]if (nextChild.key !== null) {keyToNewIndexMap.set(nextChild.key, i)}}
}

接着,定义以下几个变量

let j
let patched = 0
const toBePatched = e2 - s2 + 1 // c2 中待处理的节点数目
let moved = false// used to track whether any node has moved
let maxNewIndexSoFar = 0 // 已遍历的待处理的 c1 节点在 c2 中对应的索引最大值// works as Map
// Note that oldIndex is offset by +1
// and oldIndex = 0 is a special value indicating the new node has
// no corresponding old node.
// used for determining longest stable subsequence
const newIndexToOldIndexMap = new Array(toBePatched) // 用于后面求最长递增子序列for (i = 0; i }

然后,遍历 c1 中待处理的节点,判断否 c2 中是有相同 key 的节点存在。


  • 没有,说明该节点已经被移除,unmount。
  • 有,调用 patch 函数。 并记录节点在 c1 中的索引。同时,记录节点在 c2 中的最大索引,假如节点在 c2 中的索引位置小于这个最大索引,那么说明是有元素需要进行移动。

// 5.2 loop through old children left to be patched and try to patch
// matching nodes & remove nodes that are no longer present
for (i = s1; i <= e1; i++) {const prevChild = c1[i]// (A)let newIndex if (prevChild.key !== null) {newIndex = keyToNewIndexMap.get(prevChild.key)} else {for (j = s2; i <= e2; j++) {if (newIndexToOldIndexMap[j - s2] === 0 &&isSameVNodeType(prevChild, c2[j])) {newIndex = jbreak}}}if (newIndex === void 0) {unmount(prevChild, parentComponent, parentSuspense, true)} else {newIndexToOldIndexMap[newIndex - s2] = i + 1 // (B)if (newIndex >= maxNewIndexSoFar) {maxNewIndexSoFar = newIndex} else {moved = true}patch(prevChild,c2[i],container,null,parentComponent,parentSuspense,isSVG,optimized)patched++ // (C)}
}

是不是 c1 中的所有节点都需要在 c2 中寻找对应节点,然后调用 patch 呢。

注意到上面的代码 (C),我们会更新已经 patched 的节点的数目,那么当 patched > toBePatched,可以认为接下来遍历的 c1 中的节点都是多余的了,直接移除就好。

所以在上面的 (A) 处需要补充一下代码

if (patched >= toBePatched) {// all new children have been patched so this can only be a removalunmount(prevChild, parentComponent, parentSuspense, true)continue
}

到这里,就是较难理解的部分了。

开篇我们说过,预处理过后,剩下的节点会借助最长递增子序列来提高 diff 效率。

求解最长递增子序列,主要的目的就是为了减少 dom 元素的移动,也可以理解为最少的 dom 操作。

首先,我们需要求解得到最长递增子序列

// generate longest stable subsequence only when nodes have moved
const increasingNewIndexSequence = moved? getSequence(newIndexToOldIndexMap): EMPTY_ARR

先看看这里的 newIndexToOldIndexMap 的值是什么。

结合一下具体的例子,假设 c1 、c2 如下图所示

image

 

定义并初始化 newIndexToOldIndexMap

const newIndexToOldIndexMap = new Array(toBePatched)for (i = 0; i }

toBePatched 即预处理后,c2 中待处理的节点数目。对应这里的例子,会有

toBePatched = 4
newIndexToOldIndexMap = [0, 0, 0, 0]

注意到上面 5.2 遍历 c1 中节点的代码的 (B) 处,有

// 这里是 i + 1,不是 i
// 因为 0 是一个特殊值,表示这个是新增的节点
newIndexToOldIndexMap[newIndex - s2] = i + 1 // (B)

所以处理完 c1 中的节点后,将有

moved = true
newIndexToOldIndexMap = [4, 5, 3, 0]

那么,increasingNewIndexSequence 的值就是 getSequence(newIndexToOldIndexMap) 的返回值

// [4, 5, 3, 0] --> 最长递增子序列是 [4, 5]
// 对应的索引是 [0, 1]
increasingNewIndexSequence = [0, 1]

在求解得到最长递增子序列之后,剩下的就是遍历 c2 中的待处理节点,判断是否节点是否属于新增,是否需要进行移动。

j = increasingNewIndexSequence.length - 1
// looping backwards so that we can use last patched node as anchor
// 注意:这里是从后往前遍历
for (i = toBePatched - 1; i >= 0; i--) {const nextIndex = s2 + iconst nextChild = c2[nextIndex]const anchor =nextIndex + 1 最长递增子序列为 [] if (j <0 || i !== increasingNewIndexSequence[j]) {move(nextChild, container, anchor, MoveType.REORDER)} else {j--}}
}

最长递增子序列


在计算机科学中,最长递增子序列(longest increasing subsequence)问题是指,在一个给定的数值序列中,找到一个子序列,使得这个子序列元素的数值依次递增,并且这个子序列的长度尽可能地大。最长递增子序列中的元素在原序列中不一定是连续的。 -- 维基百科


对于以下的原始序列0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15最长递增子序列为0, 2, 6, 9, 11, 15.值得注意的是原始序列的最长递增子序列并不一定唯一,对于该原始序列,实际上还有以下两个最长递增子序列0, 4, 6, 9, 11, 15
0, 4, 6, 9, 13, 15

最后

至此,Vue 3.0 的 diff 代码就分析完了,欢迎一起讨论。

具体的代码: renderer.ts



作者:流汗去
链接:https://juejin.cn/post/6844904104834105351
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


推荐阅读
  • vue使用
    关键词: ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • PDO MySQL
    PDOMySQL如果文章有成千上万篇,该怎样保存?数据保存有多种方式,比如单机文件、单机数据库(SQLite)、网络数据库(MySQL、MariaDB)等等。根据项目来选择,做We ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 先看看ElementUI里关于el-table的template数据结构:<template><el-table:datatableData><e ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • 本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]
  • 微信民众号商城/小顺序商城开源项目介绍及使用教程
    本文介绍了一个基于WeiPHP5.0开发的微信民众号商城/小顺序商城的开源项目,包括前端和后端的目录结构,以及所使用的技术栈。同时提供了项目的运行和打包方法,并分享了一些调试和开发经验。最后还附上了在线预览和GitHub商城源码的链接,以及加入前端交流QQ群的方式。 ... [详细]
  • 如何使用计算机控制遥控车的步骤和电路制作方法
    本文介绍了使用计算机控制遥控车的步骤和电路制作方法。首先,需要检查发送器的连接器和跳线,以确定命令的传递方式。然后,通过连接跳线和地面,将发送器与电池的负极连接,以实现遥控车的前进。接下来,制作一个简单的电路,使用Arduino命令将连接到跳线的电线接地,从而实现将Arduino命令转化为发送器命令。最后,通过焊接晶体管和电阻,完成电路制作。详细的步骤和材料使用方法将在正文中介绍。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
author-avatar
放逐凌晨_690
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有