《Vue.js 设计与实现》读书笔记 - 第11章、快速 Diff 算法
第11章、快速 Diff 算法
11.1 相同的前置元素和后置元素
快速 Diff 算法包含预处理步骤,这借鉴了纯文本 Diff 算法的思路。
先把相同的前缀后缀进行处理,然后再比较中间部分。
function patchKeyedChildren(n1, n2, container) {
const oldChildren = n1.children
const newChildren = n2.children
let j = 0
let oldVNode = oldChildren[j]
let newVNode = newChildren[j]
while (oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode, container)
j++
oldVNode = oldChildren[j]
newVNode = newChildren[j]
}
let oldEnd = oldChildren.length - 1
let newEnd = newChildren.length - 1
oldVNode = oldChildren[oldEnd]
newVNode = newChildren[newEnd]
while (oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode, container)
oldEnd--
newEnd--
oldVNode = oldChildren[oldEnd]
newVNode = newChildren[newEnd]
}
}
如果旧节点都被处理完了,新节点还有剩余,证明这些都是新增的,需要依次挂载。而如果是旧节点有剩余,则需要全部卸载。
if (j > oldEnd && j <= newEnd) {
const anchorIndex = oldEnd + 1
const anchor = oldChildren[anchorIndex]
? oldChildren[anchorIndex].el
: null
while (j <= newEnd) {
patch(null, newChildren[j++], container, anchor)
}
} else if (j > newEnd && j <= oldEnd) {
while (j <= oldEnd) {
unmount(oldChildren[j++])
}
}
11.2 判断是否需要进行 DOM 移动操作
如果前缀后缀处理完之后,并没有任何一组节点被处理完,则需要进行移动操作。我们根据 key 判断相同节点,然后找到每一个新节点在旧节点中的位置,我们把这个数组存为 source。
function patchKeyedChildren(n1, n2, container) {
// ...
if (j > oldEnd && j <= newEnd) {
// ...
} else if (j > newEnd && j <= oldEnd) {
// ...
} else {
const count = newEnd - j + 1 // 新节点剩余的数量
const source = new Array(count)
source.fill(-1) // 初始值全部设为 -1
const oldStart = j
const newStart = j
for (let i = oldStart; i <= oldEnd; i++) {
const oldVNode = oldChildren[i]
for (let k = newStart; k <= newEnd; k++) {
const newVNode = newChildren[k]
if (oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode, container)
source[k - newStart] = i
}
}
}
}
}
为了在后面操作中快速找到相同 key 的节点(而不是每次都需要遍历)可以使用 Map 存储 key 对应的节点位置。而对于找不到对于 key 的节点,则需要卸载。
const count = newEnd - j + 1 // 新节点剩余的数量
const source = new Array(count)
source.fill(-1) // 初始值全部设为 -1
const oldStart = j
const newStart = j
// 对新数组构建 key->index 索引表
const keyIndex = {}
for (let i = newStart; i <= newEnd; i++) {
keyIndex[newChildren[i].key] = i
}
for (let i = oldStart; i <= oldEnd; i++) {
const oldVNode = oldChildren[i]
const k = keyIndex[oldVNode.key]
if (typeof k !== 'undefined') {
const newVNode = newChildren[k]
patch(oldVNode, newVNode, container)
source[k - newStart] = i
} else {
unmount(oldVNode)
}
}
然后我们使用第九章类似的思路来判断是否有节点需要移动。同时记录 patch 过的节点数量,当 patch 过的元素等于新节点的数量,剩下的节点直接卸载。
const count = newEnd - j + 1 // 新节点剩余的数量
const source = new Array(count)
source.fill(-1) // 初始值全部设为 -1
const oldStart = j
const newStart = j
let moved = false
let pos = 0
// 对新数组构建 key->index 索引表
const keyIndex = {}
for (let i = newStart; i <= newEnd; i++) {
keyIndex[newChildren[i].key] = i
}
let patched = 0
for (let i = oldStart; i <= oldEnd; i++) {
const oldVNode = oldChildren[i]
if (patched <= count) {
const k = keyIndex[oldVNode.key]
if (typeof k !== 'undefined') {
const newVNode = newChildren[k]
patch(oldVNode, newVNode, container)
patched++
source[k - newStart] = i
if (k < pos) {
moved = true
} else {
pos = k
}
} else {
unmount(oldVNode)
}
} else {
// 如果更新过的节点数量已经大于新的节点数量 说明剩下的节点都需要被卸载
unmount(oldVNode)
}
}
11.3 如何移动元素
我们上面通过 moved = true 标识了需要移动,下面该考虑如何移动。
我们先计算 source 的最长递增子序列。这部分不是重点,折叠了,但是我加了下注释。应该是力扣原题~
点击查看代码
function getSequence(arr) {
const p = arr.slice()
const result = [0]
let i, j, u, v, c
const len = arr.length
for (i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
j = result[result.length - 1]
if (arr[j] < arrI) {
p[i] = j
result.push(i)
continue
}
u = 0
v = result.length - 1
// 二分找到第一个大于arrI的位置
// u最小值 v最大值
while (u < v) {
c = ((u + v) / 2) | 0 // 取中间值
if (arr[result[c]] < arrI) {
u = c + 1
} else {
v = c
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
// 3 4 1
// 我们开始会存储 3 4 <0,1>
// 然后会存为 1 4 <2,1>
// 但是要知道 4 在 1 之前 不可以这样构造子序列
// 所以用p记录:一个下标被放入result时 它的前一个位置是哪个下标
p[i] = result[u - 1]
}
result[u] = i
}
}
}
u = result.length
v = result[u - 1]
while (u-- > 0) {
result[u] = v
v = p[v]
}
return result
}
console.log(getSequence([1, 2, 3])) // [ 0, 1, 2 ]
console.log(getSequence([3, 4, 1])) // [ 0, 1 ]
console.log(getSequence([3, 2, 1])) // [ 2 ]
我们通过 getSequence 计算出最长递增子序列对应的下标数组。对于最长递增子序列中包含的下标对应的节点,不进行移动,对其他节点进行移动。
方法就是从子节点数组和 lis 数组的最后一个节点作比较,如果子节点的下标等于 lis 的值,就不移动节点,同时下标向前移动,否则移动节点。
if (moved) {
const seq = lis(source)
let s = seq.length - 1
for (let i = count - 1; i >= 0; i--) {
if (source[i] === -1) {
// 新节点
const pos = i + newStart
const newVNode = newChildren[pos]
const nextPos = pos + 1
const anchor =
nextPos < newChildren.length ? newChildren[nextPos].el : null
patch(null, newVNode, container, anchor)
}
if (i !== seq[s]) {
// 移动
const pos = i + newStart
const newVNode = newChildren[pos]
const nextPos = pos + 1
const anchor =
nextPos < newChildren.length ? newChildren[nextPos].el : null
insert(newVNode.el, container, anchor)
} else {
s--
}
}
} else {
for (let i = count - 1; i >= 0; i--) {
if (source[i] === -1) {
// 新节点
const pos = i + newStart
const newVNode = newChildren[pos]
const nextPos = pos + 1
const anchor =
nextPos < newChildren.length ? newChildren[nextPos].el : null
patch(null, newVNode, container, anchor)
}
}
}
P.S.书上没有写 moved=false 相关逻辑,我看了下还是应该写的。
《Vue.js 设计与实现》读书笔记 - 第11章、快速 Diff 算法的更多相关文章
- 【vue.js权威指南】读书笔记(第一章)
最近在读新书<vue.js权威指南>,一边读,一边把笔记整理下来,方便自己以后温故知新,也希望能把自己的读书心得分享给大家. [第1章:遇见vue.js] vue.js是什么? vue.j ...
- 【vue.js权威指南】读书笔记(第二章)
[第2章:数据绑定] 何为数据绑定?答曰:数据绑定就是将数据和视图相关联,当数据发生变化的时候,可以自动的来更新视图. 数据绑定的语法主要分为以下几个部分: 文本插值:文本插值可以说是最基本的形式了. ...
- C++ primer plus读书笔记——第11章 使用类
第11章 使用类 1. 运算符重载是一种形式的C++多态. 2. 不要返回指向局部变量或临时对象的引用.函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据. 3. 运算符重载的格式如下: ...
- 《Unix环境高级编程》读书笔记 第11章-线程
1. 引言 了解如何使用多个控制线程在单进程环境中执行多个任务. 不管在什么情况下,只要单个资源需要在多个用户键共享,就必须处理一致性问题. 2. 线程概念 典型的Unix进程可以看成只有一个控制线程 ...
- 《C和指针》 读书笔记 -- 第11章 动态内存分配
1.C函数库提供了两个函数,malloc和free,分别用于执行动态内存分配和释放,这些函数维护一个可用内存池. void *malloc(size_t size);//返回指向分配的内存块起始位置的 ...
- 《JavaScript Dom 编程艺术》读书笔记-第11章
本章简单介绍了HTML5,并推荐了一个好工具Modernizr,用于检测浏览器可能支持的各种特性. HTML5的新特性包括: 可以用来在文档中绘制矢量及位图的<canvas>元素: 可以在 ...
- INSPIRED启示录 读书笔记 - 第11章 评估产品机会
市场需求文档 大多数的公司产品选择权是由高管.市场部门.开发团队甚至是大客户,在这种情况下公司会跳过市场需求文档或是误写成产品规范文档,回避评估产品机会 在正常情况下,应该是由业务人员会撰写一份论证产 ...
- Java 核心读书笔记 第11章
1. 异常 用户希望在出现错误时,程序能够采用一些理智的行为. 如果由于出现错误而使得某些操作无法完成,程序应该: 返回到一种安全状态,并能够进行一些其他的命令: 或者:允许用于保存所有操作的结果, ...
- Linux内核设计与实现 读书笔记 转
Linux内核设计与实现 读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...
- 【2018.08.13 C与C++基础】C++语言的设计与演化读书笔记
先占坑 老实说看这本书的时候,有很多地方都很迷糊,但却说不清楚问题到底在哪里,只能和Effective C++联系起来,更深层次的东西就想不到了. 链接: https://blog.csdn.net/ ...
随机推荐
- Telegram手机号码反查工具
Telegram手机号码反查工具 项目地址:https://github.com/bellingcat/telegram-phone-number-checker v1.2.1版本 要求python的 ...
- RPA+ddddocr识别图片验证码
上篇我们已经使用python识别了验证码,接下来用RPA接收python识别验证码结果进行登录 1.RPA首先安装python包 ddddocr 2.RPA新建一个python模块 编写代码 impo ...
- stable diffusion 实践与测试
stable diffusion 实践与测试 放大 原图高清放大 原始图片 当不满意图片质量的时候 使用stable diffusion进行二次处理 选择适合图片风格的模型,再次根据图片写出提示词 输 ...
- 【Kafka】03 Shell 操作
查看Kafka主题列表 $KAFKA_HOME/bin/kafka-topics.sh \ --zookeeper centos7-02:2181,centos7-03:2181,centos7-04 ...
- 【Git】03 撤销 & 版本回退
回退分为三种情况,每种情况对应了我们文件的存储区域 工作区 | 暂存区 | 版本区(当前分支) 1.文件可能存放在工作区,没有被Git追踪[红色标记状态] 2.文件可能已经添加到暂存区,没有被Git提 ...
- 使用 addRouteMiddleware 动态添加中间
title: 使用 addRouteMiddleware 动态添加中间 date: 2024/8/4 updated: 2024/8/4 author: cmdragon excerpt: 摘要:文章 ...
- 【转载】PPT上作图转化为矢量图 —— 论文写作必备技能
原文地址: https://www.cnblogs.com/scandit/p/15112138.html PPT作出的图本身就为矢量图(可以插入到Word中),另存为PDF后,再通过PDF专业工具适 ...
- 高校校园网下电脑IP是不是公网IP
突然想到一个问题,那就是高校校园网中的IP地址是不是公网IP,如果不是公网IP那么就是使用net后的共享IP,还或者是部分人用公网IP然后另一部分人使用net后的共享IP??? =========== ...
- 瑞芯微-I2S | ALSA基础-3
针对音频设备,linux内核中包含了两类音频设备驱动框架: OSS:开放声音系统 包含dsp和mixer字符设备接口,应用访问底层硬件是直接通过sound设备节点实现的: ALSA:先进linux声音 ...
- Polly服务熔断
//自定义异常 public class MyException : Exception { public MyException(string? message) : base(message) { ...