《Vue.js 设计与实现》读书笔记 - 第10章、双端 Diff 算法
第10章、双端 Diff 算法
10.1 双端比较的原理
上一章的移动算法并不是最优的,比如我们把 ABC 移动为 CAB,如下
A C
B --> A
C B
按照上一章的算法,我们遍历新的数组,然后定下第一个元素 C 的位置后,后面的 AB 都需要被移动。但是显而易见的,我们其实可以只移动 C 移动一次即可。
而使用双端 Diff 就是记录新旧两个子数组的端点,然后 新头节点-旧头结点、新尾结点-旧尾结点、旧头结点-新尾结点、旧尾结点-新头节点,这样四种组合依次去比较,直到找到匹配的元素,然后根据新节点的位置把对应的旧节点移动。
function patchChildren(n1, n2, container) {
// ... 其他逻辑省略
if (Array.isArray(n2.children)) {
// 如果新子元素是一组节点
if (Array.isArray(n1.children)) {
patchKeyedChildren(n1, n2, container)
}
}
}
function patchKeyedChildren(n1, n2, container) {
const oldChildren = n1.children
const newChildren = n2.children
// 四个索引值
let oldStartIdx = 0
let oldEndIdx = oldChildren.length - 1
let newStartIdx = 0
let newEndIdx = newChildren.length - 1
// 四个索引值对应的 vnode
let oldStartVNode = oldChildren[oldStartIdx]
let oldEndVNode = oldChildren[oldEndIdx]
let newStartVNode = newChildren[newStartIdx]
let newEndVNode = newChildren[newEndIdx]
// 循环执行四个判断条件
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVNode.key === newStartVNode.key) {
// 头部相同 不用移动节点 只需要patch打补丁
patch(oldStartVNode, newStartVNode, container)
oldStartVNode = oldChildren[++oldStartIdx]
newStartVNode = newChildren[++newStartIdx]
} else if (oldEndVNode.key === newEndVNode.key) {
// 尾部相同 不用移动节点 只需要patch打补丁
patch(oldEndVNode, newEndVNode, container)
oldEndVNode = oldChildren[--oldEndIdx]
newEndVNode = newChildren[--newEndIdx]
} else if (oldStartVNode.key === newEndVNode.key) {
// 如果旧头结点和新尾结点key相同 先patch 再把节点移到尾部
patch(oldStartVNode, newEndVNode, container)
insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling)
oldStartVNode = oldChildren[++oldStartIdx]
newEndVNode = newChildren[--newEndIdx]
} else if (oldEndVNode.key === newStartVNode.key) {
// 如果新的头结点和旧尾结点key相同 先patch 再把节点移到头部
patch(oldEndVNode, newStartVNode, container)
insert(oldEndVNode.el, container, oldStartVNode.el)
// 移动索引值
oldEndVNode = oldChildren[--oldEndIdx]
newStartVNode = newChildren[++newStartIdx]
}
}
}
10.2 双端比较的优势
使用了上述的双端 Diff,在大部分情况下可以少移动一些节点。
10.3 非理想状况下的处理方式
理想就是说每次四种条件都有一种能够命中,实际上可能全部没有命中,比如:
新 旧
2 1
4 <-- 2
1 3
3 4
这种情况时我们就处理新节点中的第一个节点。首先在旧数组中找到 key 相同的进行 patch 没有相同的就创建新节点,然后把该节点已移动到最前面。同时把旧节点置为 undefined 然后注意循环到旧节点位空时要继续前移/后移,来忽略处理过的旧节点。
// 插到循环开始位置
if (!oldStartVNode) {
oldStartVNode = oldChildren[++oldStartIdx]
} else if (!oldEndVNode) {
oldEndVNode = oldChildren[++oldEndIdx]
}
// 忽略中间那四个判断条件
const idxInOld = oldChildren.findIndex(
(node) => node?.key === newStartVNode.key
)
if (idxInOld > 0) {
const vnodeToMove = oldChildren[idxInOld]
patch(vnodeToMove, newStartVNode, container)
// 插到头部
insert(vnodeToMove.el, container, oldStartVNode.el)
// 注意处理完旧节点要在旧数组中置空
oldChildren[idxInOld] = undefined
newStartVNode = newChildren[newStartIdx++]
}
10.4 添加新元素
还是上面的情况,如果在旧节点中找不到匹配的 key,证明是新添加的元素,需要创建新节点,然后插入到头部。
const idxInOld = oldChildren.findIndex(
(node) => node?.key === newStartVNode.key
)
if (idxInOld > 0) {
const vnodeToMove = oldChildren[idxInOld]
patch(vnodeToMove, newStartVNode, container)
// 插到头部
insert(vnodeToMove.el, container, oldStartVNode.el)
// 注意处理完旧节点要在旧数组中置空
oldChildren[idxInOld] = undefined
} else {
// 将新节点挂载到头部,oldStartVNode.el 作为锚点
patch(null, newStartVNode, container, oldStartVNode.el)
}
newStartVNode = newChildren[++newStartIdx]
然后在循环结束后,如果旧数组处理完了但是新数组还有剩余,证明这些节点都是新增的,需要依次创建。
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// ...
}
if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
for (let i = newStartIdx; i <= newEndIdx; i++) {
const anchor = newChildren[newEndIdx + 1]
? newChildren[newEndIdx + 1].el
: null
patch(null, newChildren[i], container, anchor)
}
}
注意这里的 anchor 就是说我们双端 diff 的时候,如果有一些节点已经放在最后了,需要放在那些节点之前。
10.5 移除不存在的元素
如果循环完旧元素有剩余,则需要卸载。
if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
// ...
} else if (oldEndIdx >= oldStartIdx && newStartIdx > newEndIdx) {
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
unmount(oldChildren[i])
}
}
10.6 总结
这章整体还是比较好理解的,主要之前只知道双端比较,现在更清楚了匹配上之后要怎么移动。
同时这里只判断了 key 相同,在 Vue2 源码中还判断了标签等属性。
function sameVnode (a, b) {
return (
a.key === b.key &&
a.asyncFactory === b.asyncFactory && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
isUndef(b.asyncFactory.error)
)
)
)
}
《Vue.js 设计与实现》读书笔记 - 第10章、双端 Diff 算法的更多相关文章
- 【vue.js权威指南】读书笔记(第一章)
最近在读新书<vue.js权威指南>,一边读,一边把笔记整理下来,方便自己以后温故知新,也希望能把自己的读书心得分享给大家. [第1章:遇见vue.js] vue.js是什么? vue.j ...
- 【vue.js权威指南】读书笔记(第二章)
[第2章:数据绑定] 何为数据绑定?答曰:数据绑定就是将数据和视图相关联,当数据发生变化的时候,可以自动的来更新视图. 数据绑定的语法主要分为以下几个部分: 文本插值:文本插值可以说是最基本的形式了. ...
- 《JavaScript Dom 编程艺术》读书笔记-第10章
用JS实现动画~内容包括: 1. 动画基础知识 2. 用动画丰富网页的浏览效果 动画就是让元素的位置随时间而不断变化. 位置: //CSSelement{ position:absolute; top ...
- 《C++ Primer 4th》读书笔记 第10章-关联容器
原创文章,转载请注明出处:http://www.cnblogs.com/DayByDay/p/3936464.html
- 《C和指针》 读书笔记 -- 第10章 结构和联合
1.聚合数据类型能够同时存储超过一个的单独数据,c提供了两种类型的聚合数据类型,数组和结构. 2.[1] struct SIMPLE { int a; }; struct SIMPLE x; [2] ...
- css权威指南读书笔记-第10章浮动和定位
这一章看了之后真是豁然开朗,之前虽然写了圣杯布局和双飞翼布局,有些地方也是模糊的,现在打算总结之后再写一遍. 以下都是从<css权威指南>中摘抄的我认为很有用的说明. 浮动元素 一个元素浮 ...
- $《第一行代码:Android》读书笔记——第10章 Android网络编程
(一)WebView的用法 1.WebView也是一个普通的控件. 2.常用用法: WebView webView = (WebView)findViewById(R.id.web_view); we ...
- INSPIRED启示录 读书笔记 - 第10章 管理上司
十条经验 1.为项目波动做好准备:用项目波动代指让你心烦意乱的各种返工.计划变更.不要企图消灭项目波动,但是可以尽量降低其负面影响.方法是提高警惕,记录工作进度,掌握项目波动的规律,寻找对策.制订项目 ...
- 《Unix环境高级编程》读书笔记 第10章-信号
1.引言 信号是软件中断. 信号提供了一种处理异步事件的方法. 2. 信号概念 信号的名字都是以3个字符SIG开头. Linux3.2.0支持31种信号.FreeBSD.Linux和Solaris作为 ...
- C++ primer plus读书笔记——第10章 对象和类
第10章 对象和类 1. 基本类型完成了三项工作: 决定数据对象需要的内存数量: 决定如何解释内存中的位: 决定可使用数据对象执行的操作或方法. 2. 不必在类声明中使用关键字private,因为这是 ...
随机推荐
- LVM综合实验
实验目标 创建并管理一个 LVM 卷组 (VG). 在卷组中创建.扩展和缩小逻辑卷 (LV). 创建和使用快照. 实验环境 Linux 服务器 三个可用的磁盘分区: /dev/sdb1, /dev/s ...
- linux测试cpu性能的命令
linux测试cpu性能的命令 在Linux中,可以使用多种命令来测试CPU性能.以下是一些常用的命令: stress: 一个通用的压力测试工具,可以生成CPU.内存.IO等负载. 安装: sudo ...
- python高性能计算:cython使用openmp并行 —— 报错:undefined symbol: omp_get_thread_num
test.pyx文件: from cython.parallel cimport parallel from openmp cimport omp_get_thread_num cpdef void ...
- 决定了,今日起开始准备弃用京东JD
估计京东是为了节约开支,然后开始大比例的把快递物流业务进行外包了,这直接导致服务质量的直线下滑,10多年前我选择弃用当当网而选择京东JD就是因为当时当地的当当网快递是用沈阳晚报的快递上门的,快递员连P ...
- 服务器上运行 xvbf 时报错 —— Unknown encoder 'libx264'
解决方法: 使用conda环境(不具体交代) conda install ffmpeg 成功运行:
- 为什么我们需要不断的开发不同的机器学习模型 —— Do we Need Hundreds of Classifiers to Solve Real World Classification Problems?
引用: 作者:xyzh https://www.zhihu.com/question/26726794/answer/151282052 写给懒得看的人: 没有最好的分类器,只有最合适的分类器. 随机 ...
- Java学习笔记2--JDK的安装和配置
一.进入oracle官网,下载jdk oracle官网:Oracle | Cloud Applications and Cloud Platform ps:不同的浏览器,可能进入oracle官网,会只 ...
- dsu on tree 模板
dsu on tree模板运用 例题以及代码: U41492 树上数颜色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 记录详情 - 洛谷 | 计算机科学教育新生态 (luogu. ...
- 使用jquery的tmpl构建复杂表格
Tmpl提供了几种tag:${}:等同于{{=}},是输出变量,通过了html编码的.{{html}}:输出变量html,但是没有html编码,适合输出html代码.{{if }} {{else}}: ...
- 2021 CCPC 哈尔滨
gym 开场 zsy 签了 J,gjk 签了 B,我读错了 E 的题意,gjk 读对后过了 zsy 读了 K 给我,我记得是模拟赛原题,跟欧拉定理有关,但很难.他俩过了 D I,我大概会了 G 但不会 ...