VUE DIFF系列讲解

VUE 简单DIFF算法
VUE 快速DIFF算法


文章目录

  • VUE DIFF系列讲解
  • 前言
  • 一、双端DIFF的代码实现
  • 二、实践
    • 练习1
    • 2. 练习2
  • 总结

前言

本文主要讲解下双端diff, 双端diff是vue2中所用的diff算法,也是目前绝大部份面试中,大家对于vue diff回答较常用的答案。再最近的几次面试中,发现很多中级前端开发工程师,只能很模糊的描述下基本的想法,一旦给出一个具体实例进行分析,就露馅了。所以我们接下来,跟着简单的代码及demo, 再对双端diff,进行一个深入的复习


一、双端DIFF的代码实现

提示:不需要特别关注patch等函数的实现,和diff的关系不是很大。理解其大致想做的事即可,具体模拟实现可参考文末github链接
话不多说,我们直接上代码:

// 从n1和n2中分别取到oldChildren和newChildren
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) {// 在双端匹配没有匹配上的时候,会用新的startVNode在旧的里边找,如果找到了,则会把对应vnode移走,并把对应位置置为undefined,所以oldChildren某些位置会为undefined.以下!oldStartVNode和!oldEndVNode就是对上述所说情况的处理if (!oldStartVNode) {oldStartVNode = oldChildren[++oldStartIdx];} else if (!oldEndVNode) {oldEndVNode = oldChildren[--oldEndIdx];} else if (oldStartVNode.key === newStartVNode.key) {// 如果首和首相等,则说明位置没有变,仅需要打补丁patch(oldStartVNode, newStartVNode, container);// 更新索引及对应的VNodeoldStartVNode = oldChildren[++oldStartIdx];newStartVNode = newChildren[++newStartIdx];} else if (oldEndVNode.key === newEndVNode.key) {// 如果尾和尾相等,则说明位置没有变,仅需要打补丁patch(oldEndVNode, newEndVNode, container);// 更新索引及对应的VNodeoldEndVNode = oldChildren[--oldEndIdx];newEndVNode = newChildren[--newEndIdx];} else if (oldStartVNode.key === newEndVNode.key) {// 打补丁patch(oldStartVNode, newEndVNode, container);// 如果oldChildren中的第一个是newChildre最后一个,则需要把oldChildren的第一个移到最后insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling);// 更新索引及对应VNodenewEndVNode = newChildren[--newEndIdx];oldStartVNode = oldChildren[++oldStartIdx];} else if (oldEndVNode.key === newStartVNode.key) {// 打补丁patch(oldEndVNode, newStartVNode, container);// 如果oldChildren的最后一个是newChildren的第一个,则需要把oldChildren的第一个移到开始insert(oldEndVNode.el, container, oldStartVNode.el);// 更新索引及对应VNodeoldEndVNode = oldChildren[--oldEndIdx];newStartVNode = newChildren[++newStartIdx];} else {// 如果双端匹配没有找到,需要从newChildren中取出第一个,去oldChildren中进行匹配const idxInOld = oldChildren.findIndex(node => node.key === newStartVNode.key);if (idxInOld > 0) {// 需要移动到节点const vnodeToMove = oldChildren[idxInOld];// 打补丁patch(vnodeToMove, newStartVNode, container);// 如果找到了,说明oldIdx对应的VNode已经移动到首位insert(vnodeToMove.el, container, oldStartVNode.el);// 将oldChildren的对应位置,置为undefindeoldChildren[idxInOld] = undefined;} else {// 没有找到,则说明是新增节点,则挂载到对应位置const anchor = oldStartVNode.el;patch(null, newStartVNode, container, anchor);}// 更新索引及对应VNodenewStartVNode = newChildren[++newStartIdx];}
}if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {// 这种情况说明newChildren中有节点没有被遍历到,需要将未被遍历到到节点新增for (let i = newStartIdx; i <= newEndIdx; i++) {// 使用oldStartVNode才能保证不论新增遗留节点在最上/最下,放置的位置是正确的patch(null, newChildren[i], container, oldStartVNode.el);}
} else if (newStartIdx > newEndIdx && oldStartIdx <= oldEndIdx) {// 这种情况说明旧的节点还有没有匹配上的,则需要把没有匹配的节点卸载for (let i = oldStartIdx; i <= oldEndIdx; i++) {unmount(oldChildren[i]);}
}

二、实践

练习1

// 旧子节点
p-1 p-2 p-3
// 新子节点
p-4 p-2 p-1 p-3// 获取四个索引值
let oldStartIdx = 0
let oldEndIdx = 2
let newStartIdx = 0
let newEndIdx = 3// 获取到四个索引值指向的vnode
let oldStartVNode = p-1
let oldEndVNode = p-3
let newStartVNode = p-4
let newEndVNode = p-3// 进入双端循环对比
oldStartIdx(0)   newStartIdx(0)    是否相等p-1              p-4             false
oldEndIdx(2)     newEndIdx(3)      是否相等p-3              p-3             ture
因为 oldEndVNode.key === newEndVNode.key,既尾尾相等,仅进行patch,不需要移动。然后更新新旧idx及vnode
此时,真实节点为 p-1 p-2 p-3
oldEndIdx = 1         newEndIdx = 2
oldEndVNode = p-2     newEndIdx = p-1oldStartIdx(0)   newStartIdx(0)    是否相等p-1              p-4             false
oldEndIdx(1)     newEndIdx(2)      是否相等p-2              p-1             false
oldStartIdx(0)   newEndIdx(2)      是否相等p-1              p-1             ture
因为 oldStartVNode.key === newEndVNode.key, 即首尾相等,需要进行移动。因为oldStartVNode在新的中,变成了尾部节点,所以此时锚点为oldEndVNode.el.nextSibling
此时,真实节点为 p-2 p-1 p-3
oldStartIdx = 1       newEndIdx = 1
oldStartVNode = p-2   newEndIdx = p-2oldStartIdx(1)   newStartIdx(0)    是否相等p-2              p-4             false
oldEndIdx(1)     newEndIdx(1)      是否相等p-2              p-2             true
因为 oldEndVNode.key === newEndVNode.key, 即尾尾相等,不需要移动
此时,真实节点为 p-2 p-1 p-3
oldEndIdx = 0               newEndIdx = 0
oldEndVNode = undefinde     newEndIdx = p-4因oldEndIdx < oldStartIdx,所以跳出while循环此时oldEndIdx(0) < oldStartIdx(1) && newStartIdx(0) <= newEndIdx(0),所以新的子节点未被遍历完,循环新的子节点,进行挂载
newStartIdx(0) 此时对应子节点p-4 锚点为oldStartVNode.el最终,真实节点为 p-4 p-2 p-1 p-3

2. 练习2

// 旧子节点
p-1 p-2 p-3
// 新子节点
p-4 p-1 p-3 p-2// 获取四个索引值
let oldStartIdx = 0
let oldEndIdx = 2
let newStartIdx = 0
let newEndIdx = 3// 获取到四个索引值指向的vnode
let oldStartVNode = p-1
let oldEndVNode = p-3
let newStartVNode = p-4
let newEndVNode = p-2// 进入双端循环对比
oldStartIdx(0)   newStartIdx(0)    是否相等p-1              p-4             false
oldEndIdx(2)     newEndIdx(3)      是否相等p-3              p-2             false
oldStartIdx(0)   newEndIdx(3)      是否相等p-1              p-2             false
oldEndIdx(2)     newStartIdx(0)    是否相等p-1              p-4             false
因双端对比发现没有相同的,则从旧节点中遍历寻找newStartVNode, 发现找不到,则新增newStartVNode,锚点为oldStartVNode
此时,真实顺序为 p-4 p-1 p-2 p-3
更新idx及vnode
newStartIdx = 1
newStartVNode = p-1//继续双端循环
oldStartIdx(0)   newStartIdx(1)    是否相等p-1              p-1             true
因为 oldStartVNode.key === newStartVNode.key,既首首相等,仅进行patch,不需要移动。
此时,真实顺序为 p-4 p-1 p-2 p-3
更新idx及vnode
oldStartIdx = 1         newStartIdx = 2
oldStartVNode = p-2     newEndIdx = p-3//继续双端循环
oldStartIdx(1)   newStartIdx(2)    是否相等p-1              p-4             false
oldEndIdx(2)     newEndIdx(3)      是否相等p-3              p-2             false
oldStartIdx(1)   newEndIdx(3)      是否相等p-2              p-2             ture
因为 oldStartVNode.key === newEndVNode.key, 即首尾相等,需要进行移动。因为oldStartVNode在新的中,变成了尾部节点,所以此时锚点为oldEndVNode.el.nextSibling
此时,真实节点为 p-4 p-1 p-3 p-2
oldStartIdx = 2       newEndIdx = 2
oldStartVNode = p-3   newEndVNode = p-2//继续双端循环
oldStartIdx(2)   newStartIdx(2)    是否相等p-3              p-3             true
因为 oldStartVNode.key === newStartVNode.key,既首首相等,仅进行patch,不需要移动。
此时,真实顺序为  p-4 p-1 p-3 p-2
更新idx及vnode
oldStartIdx = 3               newStartIdx = 3
oldStartVNode = undefined     newStartIdx = p-2因为oldStartIdx(3) > oldEndIdx(2) && newStartIdx(3) > newEndIdx(2),所以结束循环,此时新旧节点均为又遗留,对比完成

总结

双端DIFF作为面试中出现频率较高的知识点,原理并不难,主要理解的多为一下三部分

  • 双端的遍历规则:即首(old)首(new)、尾(old)尾(new)、首(old)尾(new)、尾(old)首(new)对比
  • 上述遍历规则无可复用节点的处理情况
  • 需要进行挂载/移动时,锚点的选取规则
    只要能真正理解上述情况,面试你就成功了

参考:<<vue设计与实现>>第10章
github:link

VUE DIFF算法之双端DIFF相关推荐

  1. Vue中的Diff算法 patch函数-简单Diff算法-双端Diff算法-快速Diff算法-当数据发生改变,视图如何更新?

    文章目录 Vue中的Diff算法 概述 前置知识 patch方法 简单Diff算法 总结 双端Diff算法 --vue2 快速Diff算法 --vue3 vue2和vue3 Diff算法的区别 当数据 ...

  2. python 判断div 之间的内容是否为空_python实现数据结构与算法之双端队列实现

    简介 双端队列(deque, double-ended queue),是一种具有队列和栈的性质的数据结构.双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行.双端队列可以在队列任意一端 ...

  3. diff算法_Virtual Dom和Diff算法

    前言 这是一篇很长的文章!!!坚持看到最后有彩蛋哦!!! 文章开篇,我们先思考一个问题,大家都说 virtual dom 这,virtual dom 那的,那么 virtual dom 到底是啥? 首 ...

  4. 双端 Diff 算法原理解析及 snabbdom 简单实现

    一.准备工作 先找个放猪的容器canvas,这里宽设置了1200,高设置了600 <canvas width="1200" height="600" id ...

  5. 【面试】以面试者的角度回答Vue中的diff算法(附图示diff运算过程)

    文章目录 面试者角度回答 图示diff运算过程 掘金同人账号:

  6. 【vue设计与实现】快速Diff算法 1

    快速Diff算法,顾名思义,该算法的实测速度非常快.该算法最早应用于ivi和inferno这两个框架,Vue.js3借鉴并进行了扩展. 不同于简单Diff算法和双端Diff算法,快速Diff算法包含预 ...

  7. vue3 - diff算法之快速diff算法

    快速Diff算法 预处理 前面讲到简单Diff算法和双端Diff算法,它们使用不一样的对比规则对虚拟节点的 type(元素名)和 虚拟节点的key(唯一标识)来区分是否有可以复用的旧节点.快速Diff ...

  8. 一文搞懂Vue Diff算法

    为什么需要diff算法? 对于一个容器(比如我们常用的#app)而言,它的内容一般有三种情况: 1.字符串类型,即是文本. 2.子节点数组,即含有一个或者多个子节点 3.null,即没有子节点 在vu ...

  9. diff 算法深入一下?

    一.前言 有同学问:能否详细说一下 diff 算法. 简单说:diff 算法是一种优化手段,将前后两个模块进行差异化比较,修补(更新)差异的过程叫做 patch,也叫打补丁. 文章主要解决的问题: 1 ...

最新文章

  1. 计算机原理解读图,详细讲解仪器仪表测试系统,结构原理图拿走不谢
  2. python 项目管理_【Python基础(十三)】文件的操作(II)
  3. 《javascript高级程序设计》第六章总结
  4. php申请证书,用phpstudy来申请SSL证书
  5. Dynamics CRM On-Premise V9安装手记
  6. Java定时器Timer学习之一
  7. 特斯拉亚洲最大超级充电站正式运营,可同时提供20辆车的快充服务
  8. VMware VSphere 引发的学案(三)
  9. 服务站: WCF 消息传递基础 -- MSDN Magazine, April 2007
  10. Atitit 技术成果有哪些 1. 技术成果 1 2. 技术成果分类 2 2.1. 职务技术成果和非职务技术成果 2 2.2. 专利技术成果和非专利技术成果 2 3. 范例代码项目 与代码片段 2
  11. SlickEdit 之缘起
  12. C++排列组合及应用
  13. C语言——是否为闰年的判断
  14. 计算机主机mac地址怎么查,电脑mac地址怎么查看【图文】
  15. [Luogu P3704] [BZOJ 4816] [SDOI2017]数字表格
  16. JCEF3——谷歌浏览器内核Java版实现(一):使用jawt获取窗体句柄
  17. 【DFS】巧妙取量的倒油问题
  18. 5大模块带你了解视频后台设计(含推荐策略)
  19. 【邂逅Django】——(三)视图
  20. UOS V20 nmcli命令配置ip静态地址

热门文章

  1. 3.2 实战项目二(手工分析错误、错误标签及其修正、快速地构建一个简单的系统(快速原型模型)、训练集与验证集-来源不一致的情况(异源问题)、迁移学习、多任务学习、端到端学习)
  2. Mysql报表统计常用sql
  3. 为什么敬业签绑定了微信提醒不了?
  4. 《深入理解计算机系统》读书笔记(四)处理器体系结构
  5. BurpSuite -Repeater
  6. mysql 查询多个总和_使用MySQL查询选择多个总和,并在单独的列中显示?
  7. 将jsp文件导入页面
  8. oracle 中文导入 乱码 ZHS16GBK AL32UTF8
  9. 品钛故事:如何在淘金热中卖水? | 一点财经
  10. 2022G1工业锅炉司炉考试题库及模拟考试