当前位置: 首页 > news >正文

【vue2源码学习】— diff

vue更新还是调用了 vm._update
会进入下面这一步
vm.$el = vm.__patch__(prevVnode, vnode)
又回到了patch方法
会通过sameVnode 判断是不是相同的vnode
// patch代码片段
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root nodepatchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}function sameVnode (a, b) {return (a.key === b.key && ((a.tag === b.tag &&a.isComment === b.isComment &&isDef(a.data) === isDef(b.data) &&sameInputType(a, b)) || (isTrue(a.isAsyncPlaceholder) &&a.asyncFactory === b.asyncFactory &&isUndef(b.asyncFactory.error))))
}先判断新旧key是否相等
不相等直接判定为不同
相同继续判断上面那些属性的相等性

新旧不同

如果新旧 vnode 不同,只需要替换已存在的节点
patch继续往下执行
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(vnode,insertedVnodeQueue,oldElm._leaveCb ? null : parentElm,nodeOps.nextSibling(oldElm)
)
旧节点为参考节点,创建新的节点,并插入到 DOM// update parent placeholder node element, recursively
// 更新父节点的占位符节点
if (isDef(vnode.parent)) {...}
// 删除旧节点
if (isDef(parentElm)) {removeVnodes(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode)
}

新旧相同

调用 patchVNode 方法
function patchVnode (oldVnode,vnode,insertedVnodeQueue,ownerArray,index,removeOnly) {if (oldVnode === vnode) {return}...预处理const oldCh = oldVnode.childrenconst ch = vnode.childrenif (isDef(data) && isPatchable(vnode)) {for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)}if (isUndef(vnode.text)) {//oldCh 与 ch 都存在且不相同时,使用 updateChildren 函数来更新子节点if (isDef(oldCh) && isDef(ch)) {if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)} else if (isDef(ch)) {//只有 ch 存在,那么旧节点就不需要了。//如果旧节点是文本节点则先将节点的文本清空,//然后通过 addVnodes 将 ch 批量插入到 elm 下。if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)} else if (isDef(oldCh)) {//只有 oldCh 存在,表示更新的是空节点,//removeVnodes将旧的节点全部清除removeVnodes(oldCh, 0, oldCh.length - 1)} else if (isDef(oldVnode.text)) {//只有旧节点是文本节点的时候,清除文本内容。nodeOps.setTextContent(elm, '')}} else if (oldVnode.text !== vnode.text) {//vnode 是个文本节点且新旧文本不相同,则直接替换文本内容nodeOps.setTextContent(elm, vnode.text)}...}
vue中的diff核心函数updateChildren
vue中的diff新旧的数据结构都是数组,通过双指针算法和一些参数进行的;
(react中的diff是新节点是数组,旧节点是链表,根据新旧的位置和key设计的算法进行的)
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {let oldStartIdx = 0let newStartIdx = 0let oldEndIdx = oldCh.length - 1let oldStartVnode = oldCh[0]let oldEndVnode = oldCh[oldEndIdx]let newEndIdx = newCh.length - 1let newStartVnode = newCh[0]let newEndVnode = newCh[newEndIdx]let oldKeyToIdx, idxInOld, vnodeToMove, refElm// removeOnly is a special flag used only by <transition-group>// to ensure removed elements stay in correct relative positions// during leaving transitionsconst canMove = !removeOnlyif (process.env.NODE_ENV !== 'production') {checkDuplicateKeys(newCh)}while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {//oldStartVnode 不存在 oldStartIdx 继续向中间靠拢if (isUndef(oldStartVnode)) {oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left} else if (isUndef(oldEndVnode)) {// oldEndVnode 不存在 oldEndIdx 继续向中间靠拢oldEndVnode = oldCh[--oldEndIdx]} else if (sameVnode(oldStartVnode, newStartVnode)) {//对比新旧的左边patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)oldStartVnode = oldCh[++oldStartIdx]newStartVnode = newCh[++newStartIdx]} else if (sameVnode(oldEndVnode, newEndVnode)) {// 对比新旧的右边patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)oldEndVnode = oldCh[--oldEndIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right// 旧的左边和新的右边比较, 处理旧节点前面的节点移动到后边的情况patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))oldStartVnode = oldCh[++oldStartIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left//旧的右边和新的左边比较, 处理旧节点移动到前面的情况patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)oldEndVnode = oldCh[--oldEndIdx]newStartVnode = newCh[++newStartIdx]} else {// 以上都没命中通过key创建map,找相同的节点if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)idxInOld = isDef(newStartVnode.key)? oldKeyToIdx[newStartVnode.key]: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)// 如果没找到就创建一个新的if (isUndef(idxInOld)) { // New elementcreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)} else {// 找到了同类型节点更新处理vnodeToMove = oldCh[idxInOld]if (sameVnode(vnodeToMove, newStartVnode)) {patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)oldCh[idxInOld] = undefinedcanMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)} else {// 相同的key但是不同的元素,也创建新的// same key but different element. treat as new elementcreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)}}newStartVnode = newCh[++newStartIdx]}}if (oldStartIdx > oldEndIdx) {refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elmaddVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)} else if (newStartIdx > newEndIdx) {removeVnodes(oldCh, oldStartIdx, oldEndIdx)}}oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx表示
一直循环处理直到旧的节点出现交叉或者新的节点出现交叉
oldStartIdx > oldEndIdx表示旧节点先交叉,新节点有多的
所以新增节点
newStartIdx > newEndIdx表示新节点先交叉,旧节点有多的
删除多余的旧节点---
nodeOps.insertBefore方法实际调用的就是dom的insertBefore方法
export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {parentNode.insertBefore(newNode, referenceNode)
}
// 参数解释
newNode:将要插入的节点
referenceNode:被参照的节点(即要插在该节点之前)
parentNode:父节点这个方法的作用:
如果给定的子节点是对文档中现有节点的引用,
insertBefore()会将其从当前位置移动到新位置。
如果引用节点为空,则将指定的节点添加到指定父节点的子节点列表的末尾
条件分支:
else if (sameVnode(oldEndVnode, newStartVnode)
else if (sameVnode(oldStartVnode, newEndVnode)
这两句其实是对react那种前面的节点移动到后面的优化
react中会根据位置作为算法的条件之一
(假设都是可以复用的节点)简单来说就是
旧结构里面越靠后的节点移动到了前面,这个节点会标记为不动
但是旧结构里面在它之前的节点都会被标记为移动
所以这两句分支就是对这种情况的优化我们从源码可以看出vue对这种情况是没办法的
需要依赖key进行遍历更新
: A->B->C->D key也是ABCD
: B->A->D->C所以vue进行更新节点的时候尽量不要把节点位置交错

总结

vue的diff使用双指针算法,进行新旧对比
假设:
旧: A->B->C->D key也是ABCD
新: B->A->D->C
(过一遍逻辑)
1.先判断 旧A是不是未定义,是的话旧的左指针向右一步2.判断 旧D是不是未定义,是的话旧的右指针向左一步3.判断 旧A和新B是不是sameVnode,
是的话继续patch并且新旧左指针右移一步
下次比较旧B新A4.判断 旧D和新C是不是sameVnode,
是的话继续patch并且新旧右指针左移一步,
下次比较旧C新D5.判断 旧A和新C是不是sameVnode,
是的话继续patch并且把旧A移动到旧D的兄弟节点前面
(如果没有会移动父节点的最后),
旧左指针右移一步,新右指针左移一步,下次比较旧B新D6.判断 旧D和新B是不是sameVnode,是的话继续patch
并且把旧D移动到旧A的前面,
旧右指针左移一步,新左指针右移一步,下次比较旧C新A7.以上都没命中,createKeyToOldIdx拿到key的map,
newStartVnode.key是否存在
存在就从map拿没有就遍历判断新B在A->B->C->D的位置,
没有就新建,
有就判断这个节点和新的左指针做对应的节点是否相同
相同移动这个节点到旧左指针元素之前
不同创建新的
新左指针右移一步下次比较新A旧A直到新或者旧的指针出现交叉
然后处理剩下的节点8.如果旧的先交叉说明新的多了,新增节点
9.如果新的先交叉说明新的少了,删除节点

相关文章:

【vue2源码学习】— diff

vue更新还是调用了 vm._update 会进入下面这一步 vm.$el vm.__patch__(prevVnode, vnode) 又回到了patch方法 会通过sameVnode 判断是不是相同的vnode// patch代码片段 const isRealElement isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vno…...

更换 Linux 自带的 jdk 环境

如下&#xff0c;我要把 Linux 默认的 jdk 版本换成我自己的 jdk 版本。 Linux 自带的 jdk 环境&#xff1a; 要更换的 jdk 环境&#xff1a; 1、切换到 root 用户进行操作&#xff1b; 2、在根目录下创建一个 /export/server/ 目录&#xff1b; [rootcentos /]# mkdir -p /e…...

MySQL8读写分离集群

文章目录前言MySQL读写分离原理搭建MySQL读写分离集群MySQL8.0之前MySQL8.0之后后记前言 上一期介绍并实现了MySQL的主从复制&#xff0c;由于主从复制架构仅仅能解决数据冗余备份的问题&#xff0c;从节点不对外提供服务&#xff0c;依然存在单节点的高并发问题 所以在主从复…...

蓝桥冲刺31天之第七天

目录 A&#xff1a;三角回文数 B&#xff1a;数数 C&#xff1a;数组切分 D&#xff1a;倍数问题 一星陨落&#xff0c;黯淡不了星空灿烂&#xff1b; 一花凋零&#xff0c;荒芜不了整个春天。 如果命运是世界上最烂的编剧&#xff0c; 你就要争取做人生最好的演员。 即使生…...

【Python百日进阶-Web开发-Vue3】Day550 - Vue3 商城后台 10:Veux4-02基本使用

文章目录 二、Vuex的基本使用2.4 Mutations 应用 :同步更新state2.4.1 `src/store/index.js`2.4.2 `src/views/index.vue`2.5 Module的应用:分模块2.5.1 `src/store/modules/product.js`2.5.2 `src/store/modules/cart.js`2.5.3 `src/store/index.js`2.5.4 `src/views/index.…...

ESP32驱动-红外寻迹传感器驱动

红外寻迹传感器驱动 1、红外寻迹传感器介绍 红外寻迹传感器具有一对红外线发射管与接收管,发射管发射出一定频率的红外线,当检测方向遇到障碍物(反射面)时,红外线反射回来被接收管接收,经过比较器电路处理之后,输出接口会输出一个数字信号(低电平或高电平,取决于电路…...

【TS】TypeScript泛型 T 的用法详解

一、什么是泛型&#xff1f; 泛型&#xff0c;从字面上理解&#xff0c;泛型就是一般的&#xff0c;广泛的的意思。 TypeScript中泛型&#xff08;Generics&#xff09;是指在定义函数、接口或类的时候&#xff0c;不预先指定具体类型&#xff0c;而是在使用的时候再指定类型…...

Vue 3.0 单文件组件 【Vue3 从零开始】

#介绍 在很多 Vue 项目中&#xff0c;我们使用 app.component 来定义全局组件&#xff0c;紧接着用 app.mount(#app) 在每个页面内指定一个容器元素。 这种方式在很多中小规模的项目中运作的很好&#xff0c;在这些项目里 JavaScript 只被用来加强特定的视图。但当在更复杂的…...

北邮22信通:你是不是在looking for……那串代码?(2)第三章单链表

相信有了第二章顺序表的基础&#xff0c;小伙伴们学习第三章链表应该会轻松一点吧 目录 类模板下的单链表 1.1书上干净完整代码&#xff08;无增改、适合自己动手实验&#xff09; 1.2对书上代码的完善和对一些问题的验证和解释代码 1.补全一个函数&#xff1a; 2.this指…...

蓝库云|告诉你传统产业该如何进行数字化转型

在后疫情时代下&#xff0c;企业该如何在面临生存危机的情形下&#xff0c;投入「数字化转型」、提升公司竞争力&#xff0c;已成为许多公司的当务之急&#xff0c;但到底什么是数字化转型呢&#xff1f;传统产业又如何着手进行数位转型&#xff1f; 数字化转型是什么&#xf…...

121.(leaflet篇)leaflet结合echarts4迁徙图

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYPE html> <html>...

链表及其基本操作

1.单链表&#xff1a;1.1定义/性质&#xff1a;链表是线性表的链式存储方式。单链表通过指针线性遍历&#xff0c;删除/增加节点时间复杂度为O(1&#xff09;,访问节点时间复杂度为O(n)。单链表分为带头结点和不带头结点两种&#xff0c;带头结点是为了方便统一操作&#xff08…...

【Java基础 下】 031 -- 反射 动态代理

一、什么是反射&#xff1f; 换句话说就是&#xff08;从类里拿出来&#xff09; 可以获取到&#xff1a;&#xff08;利用反射&#xff0c;我们可以获取到类中所有的东西&#xff09; 获取是先从class字节码文件中获取的 二、获取class对象的三种方式 三种方式也对应了三种阶段…...

springcloud3 GateWay

一 GateWay 1.1 GateWay的作用 gateway相当于所有服务的门户&#xff0c;将客户端请求与服务端应用相分离&#xff0c;客户端请求通过gateway后由定义的路由和断言进行转发&#xff0c;路由代表需要转发请求的地址&#xff0c;断言相当于请求这些地址时所满足的条件&#xff…...

万字长文:Stable Diffusion 保姆级教程

万字长文&#xff1a;Stable Diffusion 保姆级教程 2022年绝对是人工智能爆发的元年&#xff0c;前有 stability.ai 开源 Stable Diffusion 模型&#xff0c;后有 Open AI 发布 ChatGPT&#xff0c;二者都是里程碑式的节点事件&#xff0c;其重要性不亚于当年苹果发布iPhone&a…...

WAMP搭建靶场

WAMP W&#xff1a;windows A&#xff1a;apache M&#xff1a;mysql&#xff0c;mariadb P&#xff1a;php 1. 下载phpstudy Windows版phpstudy下载 - 小皮面板(phpstudy) 2. 安装phpstudy 默认安装即可 3. 下载DVWA靶场 https://github.com/digininja/DVWA/archive/…...

Uipath Excel 自动化系列13-ForEachExcelSheet(遍历Sheet)

活动描述 ForEachExcelSheet(遍历Sheet)&#xff1a;遍历Excel中的工作表&#xff0c;可以对 Excel 工作簿中的每个工作表重复一个或多个活动,该活动需与Use Excel File 活动选择的 Excel 文件一起使用。 使用场景&#xff1a;当处理包含多张工作表的 Excel 文件&#xff0c;…...

JDBC快速入门

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; 目录 一、JDBC入门 1.概述 (1)JDBC本质 (2)JDBC好处 2.快速入门 (1)步骤 (2)实践 (3)两个小问题 一、JDBC入门 1.概述 JDBC就是使用Java语言操作关系型数据库的一套API&#xff0c;全称&#xff1a;( Java…...

蓝桥杯三月刷题 第六天

文章目录&#x1f4a5;前言&#x1f609;解题报告&#x1f4a5;星期计算&#x1f914;一、思路:&#x1f60e;二、代码&#xff1a;&#x1f4a5;考勤刷卡&#x1f914;一、思路:&#x1f60e;二、代码&#xff1a;&#x1f4a5;卡片&#x1f914;一、思路:&#x1f60e;二、代…...

分享几个常用的运维 shell 脚本

今天咸鱼给大家分享几个不错的 Linux 运维脚本&#xff0c;这些脚本中大量使用了 Linux 的文本三剑客&#xff1a; awkgrepsed 建议大家这三个工具都要了解并最好能够较为熟练的使用 根据 PID 显示进程所有信息 根据用户输入的PID&#xff0c;过滤出该PID所有的信息 #! /b…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

Ubuntu系统复制(U盘-电脑硬盘)

所需环境 电脑自带硬盘&#xff1a;1块 (1T) U盘1&#xff1a;Ubuntu系统引导盘&#xff08;用于“U盘2”复制到“电脑自带硬盘”&#xff09; U盘2&#xff1a;Ubuntu系统盘&#xff08;1T&#xff0c;用于被复制&#xff09; &#xff01;&#xff01;&#xff01;建议“电脑…...