《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/ ...
随机推荐
- LeetCode654. 最大二叉树
题目链接:https://leetcode.cn/problems/maximum-binary-tree/description/ 题目叙述 给定一个不重复的整数数组 nums . 最大二叉树 可以 ...
- 【Hibernate】05 缓存与MySQL事务隔离
Cache 什么是缓存? 数据存储到数据库,是从内存中以流的方式写进[输出]到数据库,其效率并不是很高 - 所以在内存中暂存一部分数据,可以不以流的方式读取,效率是非常高的[相对于流来说] Hiber ...
- 【Java-GUI】09 Swing03 对话框
消息弹出框案例: package cn.dzz.swing; import javax.swing.*; import java.awt.*; import java.awt.event.Action ...
- 【MySQL】30 备份与恢复
1.备份命令: mysqldump -u用户名 -p 密码 -h 服务主机IP -P 端口号 \ 数据库名称 \ > 指定备份的sql脚本文件位置 ↓ # 文件位置样例: # C:\Users\ ...
- 灵巧度最高的机械手 —— Clone公司
地址: https://www.youtube.com/watch?v=ikrDqfnZNLU Clone 公司: 生产通过液压驱动的仿生机器人.
- 《Python数据可视化之matplotlib实践》 源码 第二篇 精进 第五章
图 5.1 import matplotlib.pyplot as plt import numpy as np from matplotlib.ticker import AutoMinorLoca ...
- MindSpore社区的小礼物
前段时间在gitee上给MindSpore提过几个问题,后来一直没有回复,前几天得到了回复又通过网络视频聊了聊自己认为该开源项目还有哪些不足的地方,最后获赠了个小书包,对这个小礼物还是蛮喜欢的,下面给 ...
- 树莓派3b+ 安装windows10 arm版本的方法及使用体验
首先,我再网上找到了一个很详细的为树莓派3b安装windows10 arm的教程,实际操作下来发现并不可行. 最后找到了可行的教程: 第3章 将Windows10镜像写入TF卡:https://zhu ...
- 海豚调度调优 | 如何解决任务被禁用出现的Bug
本系列文章是 DolphinScheduler 由浅入深的教程,涵盖搭建.二开迭代.核心原理解读.运维和管理等一系列内容.适用于想对 DolphinScheduler了解或想要加深理解的读者. 祝开卷 ...
- 重磅预告!Apache DolphinScheduler 3.2.0 新功能“剧透”
近期,Apache DolphinScheduler 将迎来 3.2.0 版本的到来.本次发版为大版本发布,将会带来众多大家期待已久的新功能和新改进.为了让用户提前感知到新版本的变化,社区特意提前&q ...