Vue源码系列讲解——虚拟DOM篇【三】(更新子节点)

news2025/1/18 9:04:14

1. 前言

在上一篇文章中,我们了解了Vue中的patch过程,即DOM-Diff算法。并且知道了在patch过程中基本会干三件事,分别是:创建节点,删除节点和更新节点。创建节点和删除节点都比较简单,而更新节点因为要处理各种可能出现的情况所以逻辑略微复杂一些,但是没关系,我们通过分析过程,对照源码,画逻辑流程图来帮助我们理解了其中的过程。最后我们还遗留了一个问题,那就是在更新节点过程中,新旧VNode可能都包含有子节点,对于子节点的对比更新会有额外的一些逻辑,那么在本篇文章中我们就来学习在Vue中是怎么对比更新子节点的。

2. 更新子节点

当新的VNode与旧的oldVNode都是元素节点并且都包含子节点时,那么这两个节点的VNode实例上的children属性就是所包含的子节点数组。我们把新的VNode上的子节点数组记为newChildren,把旧的oldVNode上的子节点数组记为oldChildren,我们把newChildren里面的元素与oldChildren里的元素一一进行对比,对比两个子节点数组肯定是要通过循环,外层循环newChildren数组,内层循环oldChildren数组,每循环外层newChildren数组里的一个子节点,就去内层oldChildren数组里找看有没有与之相同的子节点,伪代码如下:

for (let i = 0; i < newChildren.length; i++) {
  const newChild = newChildren[i];
  for (let j = 0; j < oldChildren.length; j++) {
    const oldChild = oldChildren[j];
    if (newChild === oldChild) {
      // ...
    }
  }
}

那么以上这个过程将会存在以下四种情况:

  • 创建子节点

    如果newChildren里面的某个子节点在oldChildren里找不到与之相同的子节点,那么说明newChildren里面的这个子节点是之前没有的,是需要此次新增的节点,那么就创建子节点。

  • 删除子节点

    如果把newChildren里面的每一个子节点都循环完毕后,发现在oldChildren还有未处理的子节点,那就说明这些未处理的子节点是需要被废弃的,那么就将这些节点删除。

  • 移动子节点

    如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点,但是所处的位置不同,这说明此次变化需要调整该子节点的位置,那就以newChildren里子节点的位置为基准,调整oldChildren里该节点的位置,使之与在newChildren里的位置相同。

  • 更新节点

    如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点,并且所处的位置也相同,那么就更新oldChildren里该节点,使之与newChildren里的该节点相同。

OK,到这里,逻辑就相对清晰了,接下来我们只需分门别类的处理这四种情况就好了。

3. 创建子节点

如果newChildren里面的某个子节点在oldChildren里找不到与之相同的子节点,那么说明newChildren里面的这个子节点是之前没有的,是需要此次新增的节点,那么我们就创建这个节点,创建好之后再把它插入到DOM中合适的位置。

创建节点这个很容易,我们在上一篇文章的第三章已经介绍过了,这里就不再赘述了。

那么创建好之后如何插入到DOM中的合适的位置呢?显然,把节点插入到DOM中是很容易的,找到合适的位置是关键。接下来我们分析一下如何找这个合适的位置。我们看下面这个图: 

上图中左边是新的VNode,右边是旧的oldVNode,同时也是真实的DOM。这个图意思是当我们循环newChildren数组里面的子节点,前两个子节点都在oldChildren里找到了与之对应的子节点,那么我们将其处理,处理过后把它们标志为已处理,当循环到newChildren数组里第三个子节点时,发现在oldChildren里找不到与之对应的子节点,那么我们就需要创建这个节点,创建好之后我们发现这个节点本是newChildren数组里左起第三个子节点,那么我们就把创建好的节点插入到真实DOM里的第三个节点位置,也就是所有已处理节点之后,OK,此时我们拍手称快,所有已处理节点之后就是我们要找的合适的位置,但是真的是这样吗?我们再来看下面这个图: 

假如我们按照上面的方法把第三个节点插入到所有已处理节点之后,此时如果第四个节点也在oldChildren里找不到与之对应的节点,也是需要创建的节点,那么当我们把第四个节点也按照上面的说的插入到已处理节点之后,发现怎么插入到第三个位置了,可明明这个节点在newChildren数组里是第四个啊!

这就是问题所在,其实,我们应该把新创建的节点插入到所有未处理节点之前,这样以来逻辑才正确。后面不管有多少个新增的节点,每一个都插入到所有未处理节点之前,位置才不会错。

所以,合适的位置是所有未处理节点之前,而并非所有已处理节点之后

4. 删除子节点

如果把newChildren里面的每一个子节点都循环一遍,能在oldChildren数组里找到的就处理它,找不到的就新增,直到把newChildren里面所有子节点都过一遍后,发现在oldChildren还存在未处理的子节点,那就说明这些未处理的子节点是需要被废弃的,那么就将这些节点删除。

删除节点这个也很容易,我们在上一篇文章的第四章已经介绍过了,这里就不再赘述了。

5. 更新子节点

如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点,并且所处的位置也相同,那么就更新oldChildren里该节点,使之与newChildren里的该节点相同。

关于更新节点,我们在上一篇文章的第五章已经介绍过了,这里就不再赘述了。

6. 移动子节点

如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点,但是所处的位置不同,这说明此次变化需要调整该子节点的位置,那就以newChildren里子节点的位置为基准,调整oldChildren里该节点的位置,使之与在newChildren里的位置相同。

同样,移动一个节点不难,关键在于该移动到哪,或者说关键在于移动到哪个位置,这个位置才是关键。我们看下图: 

在上图中,绿色的两个节点是相同节点但是所处位置不同,即newChildren里面的第三个子节点与真实DOMoldChildren里面的第四个子节点相同但是所处位置不同,按照上面所说的,我们应该以newChildren里子节点的位置为基准,调整oldChildren里该节点的位置,所以我们应该把真实DOMoldChildren里面的第四个节点移动到第三个节点的位置,通过上图中的标注我们不难发现,所有未处理节点之前就是我们要移动的目的位置。如果此时你说那可不可以移动到所有已处理节点之后呢?那就又回到了更新节点时所遇到的那个问题了:如果前面有新增的节点呢?

7. 回到源码

OK,以上就是更新子节点时所要考虑的所有情况了,分析完以后,我们回到源码里看看实际情况是不是我们分析的这样子的,源码如下:

// 源码位置: /src/core/vdom/patch.js

if (isUndef(idxInOld)) {    // 如果在oldChildren里找不到当前循环的newChildren里的子节点
    // 新增节点并插入到合适位置
    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
    // 如果在oldChildren里找到了当前循环的newChildren里的子节点
    vnodeToMove = oldCh[idxInOld]
    // 如果两个节点相同
    if (sameVnode(vnodeToMove, newStartVnode)) {
        // 调用patchVnode更新节点
        patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
        oldCh[idxInOld] = undefined
        // canmove表示是否需要移动节点,如果为true表示需要移动,则移动节点,如果为false则不用移动
        canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
    }
}

以上代码中,首先判断在oldChildren里能否找到当前循环的newChildren里的子节点,如果找不到,那就是新增节点并插入到合适位置;如果找到了,先对比两个节点是否相同,若相同则先调用patchVnode更新节点,更新完之后再看是否需要移动节点,注意,源码里在判断是否需要移动子节点时用了简写的方式,下面这两种写法是等价的:

canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
// 等同于
if(canMove){
    nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
}

我们看到,源码里的实现跟我们分析的是一样一样的。

8. 总结

本篇文章我们分析了Vue在更新子节点时是外层循环newChildren数组,内层循环oldChildren数组,把newChildren数组里的每一个元素分别与oldChildren数组里的每一个元素匹配,根据不同情况作出创建子节点、删除子节点、更新子节点以及移动子节点的操作。并且我们对不同情况的不同操作都进行了深入分析,分析之后我们回到源码验证我们分析的正确性,发现我们的分析跟源码的实现是一致的。

最后,我们再思考一个问题:这样双层循环虽然能解决问题,但是如果节点数量很多,这样循环算法的时间复杂度会不会很高?有没有什么可以优化的办法?答案当然是有的,并且Vue也意识到了这点,也进行了优化,那么下篇文章我们就来分析当节点数量很多时Vue是怎么优化算法的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1442054.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

npm 上传一个自己的应用(2) 创建一个JavaScript函数 并发布到NPM

上文 npm 上传一个自己的应用(1) 搭建一个项目环境 带着大家创建了一个项目环境 我们打开 看json的配置 我们入口是一个叫 index.js 的文件 那么 我们就要把它创建出来 之后 我们的方法也就要写在这里面 和 json同一个目录 创建 index.js 我们这里 写个简单的求和操作 index…

清理神器CleanMyMac X 空间透镜——可视化您的磁盘空间 空间透镜有什么用

不久前&#xff0c;CleanMyMac X 发布了一个新功能&#xff1a; 空间透镜 相信有非常多的小伙伴和小编一样&#xff0c; 对这个功能一脸问号 这啥玩意儿&#xff1f;&#xff1f;&#xff1f; 今天就让我们深入了解一下&#xff0c; CleanMyMac X 的空间透镜功能。 - 更好…

故障诊断 | 一文解决,TCN时间卷积神经网络模型的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,TCN时间卷积神经网络模型的故障诊断(Matlab) 模型描述 时间卷积神经网络(TCN)是一种用于序列数据建模和预测的深度学习模型。它通过卷积操作在时间维度上对序列数据进行特征提取,并且可以处理可变长度的输入序列。 要使用TCN进行…

联合体知识点解析

联合体&#xff1a; 联合体也是一种自定义类型&#xff0c; 特点是成员变量公用一块空间。所以也叫共用体。 联合体的性质 先定义一个联合体&#xff1a; 然后我创建一个联合体变量&#xff1a; 现在探究当修改一个成员变量的值时&#xff0c; 其他成员变量的值能否被修改&am…

精雕细琢的文档体验:Spring Boot 与 Knife4j 完美交汇

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 精雕细琢的文档体验&#xff1a;Spring Boot 与 Knife4j 完美交汇 前言Knife4j 与 Swagger 的区别1. 特性与优劣势对比&#xff1a;Knife4j&#xff1a;Swagger&#xff1a; 2. 选择 Knife4j 的理由&a…

[C#]winform制作仪表盘好用的表盘控件和使用方法

【仪表盘一般创建流程】 在C#中制作仪表盘文案&#xff08;通常指仪表盘上的文本、数字或指标显示&#xff09;涉及到使用图形用户界面&#xff08;GUI&#xff09;组件&#xff0c;比如Windows Forms、WPF (Windows Presentation Foundation) 或 ASP.NET 等。以下是一个使用W…

美创科技与河南金融信创生态实验室签署战略合作协议

2024年1月31日&#xff0c;由普惠通科技与河南省科学院物理所、北京交通大学、中国金融电子化集团重庆金融认证中心联合发起成立中部地区第一家金融信创生态实验室运营公司&#xff08;即河南豫科普惠通信创科技有限公司&#xff09;与杭州美创科技股份有限公司战略合作签约仪式…

KtConnect 本地连接连接K8S工具

KT Connect简介 Kt Connect &#xff08;Kubernetes Developer Tool&#xff09;是一个阿里开源、轻量级的面向 Kubernetes 用户的开发测试环境治理辅助工具。其核心是通过建立本地到集群以及集群到本地的双向通道。 1.阿里开源&#xff0c;轻量级, 2. 安装快捷简单&#xf…

Java基础常见面试题总结-集合(一)

常见的集合有哪些&#xff1f; Java集合类主要由两个接口Collection和Map派生出来的&#xff0c;Collection有三个子接口&#xff1a;List、Set、Queue。 Java集合框架图如下&#xff1a; List代表了有序可重复集合&#xff0c;可直接根据元素的索引来访问&#xff1b;Set代表…

Linux 36.2@Jetson Orin Nano基础环境构建

Linux 36.2Jetson Orin Nano基础环境构建 1. 源由2. 步骤2.1 安装NVIDIA Jetson Linux 36.2系统2.2 必备软件安装2.3 基本远程环境2.3.1 远程ssh登录2.3.2 samba局域网2.3.3 VNC远程登录 2.4 开发环境安装 3. 总结 1. 源由 现在流行什么&#xff0c;也跟风来么一个一篇。当然&…

containerd中文翻译系列(十九)cri插件

cri插件包含的内容比较多&#xff0c;阅读之前请深呼吸三次、三次、三次。 CRI 插件的架构 本小节介绍了 containerd 的 cri 插件的架构。 该插件是 Kubernetes 容器运行时接口&#xff08;CRI&#xff09; 的实现。Containerd与Kubelet在同一个节点上运行。containerd内部的…

关于域名递归解析服务的问题

域名递归解析服务是互联网基础设施的重要组成部分&#xff0c;它允许用户通过域名来访问网站或应用程序。然而&#xff0c;在某些情况下&#xff0c;域名递归解析服务可能会出现问题&#xff0c;导致用户无法正常访问网站或应用程序。本文将探讨域名递归解析服务可能面临的问题…

【C++第二阶段】运算符重载-【+】【cout】【++|--】

你好你好&#xff01; 以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 运算符重载加法运算符重载重载左移运算符递增|减运算符重载 运算符重载 加法运算符重载 What 普通的加减乘除&#xff0c;只能应付C中已给定的数据类型的运…

SFML(1) | 自由落体小球

SFML(1) | 自由落体小球 文章目录 SFML(1) | 自由落体小球1. 目的2. SFML 适合做图形显示的理由3. 使用 SFML - 构建阶段4. 使用 SFML - C 代码5. 运行效果6. 总结7. References 1. 目的 通过一些简单的例子&#xff08;2D小游戏的基础代码片段&#xff09;&#xff0c; 来学习…

Python 小白的 Leetcode Daily Challenge 刷题计划 - 20240209(除夕)

368. Largest Divisible Subset 难度&#xff1a;Medium 动态规划 方案还原 Yesterdays Daily Challenge can be reduced to the problem of shortest path in an unweighted graph while todays daily challenge can be reduced to the problem of longest path in an unwe…

互联网加竞赛 基于深度学习的目标检测算法

文章目录 1 简介2 目标检测概念3 目标分类、定位、检测示例4 传统目标检测5 两类目标检测算法5.1 相关研究5.1.1 选择性搜索5.1.2 OverFeat 5.2 基于区域提名的方法5.2.1 R-CNN5.2.2 SPP-net5.2.3 Fast R-CNN 5.3 端到端的方法YOLOSSD 6 人体检测结果7 最后 1 简介 &#x1f5…

害怕跟别人进行社交,怎么办?

前几天&#xff0c;跟一位朋友&#xff0c;小聚了一下。 这位朋友&#xff0c;在一家大型 IT 公司里当技术主管。收入不低&#xff0c;烟酒不沾&#xff0c;常常健身&#xff0c;外型不错&#xff0c;为人也踏实可靠。除了有一点技术宅的死板之外&#xff0c;可以说是非常理想的…

小项目:蓝牙模块点亮RGB三色灯

在之前的教程中&#xff0c;我们学习了蓝牙模块的原理&#xff0c;并动手写了驱动&#xff0c;实现了串口的接收和发送。本次我们就来教大家如何使用蓝牙串口控制灯。这是一个简单的示例&#xff0c;展示了如何将蓝牙通信与硬件控制相结合&#xff0c;实现远程控制的功能。你也…

微软Windows生态是怎么打造成功的?

&#xff08;1&#xff09;2015年Windows10&#xff1a;兼容性 我不得不再次佩服一下微软&#xff0c;Windows10是2015年出品的&#xff0c;但是仍然能正常运行绝大多数的Windows95软件&#xff0c;不用做任何的适配修改&#xff0c;连重新编译都不用&#xff0c;运行照样正常。…

AcWing 1224 交换瓶子(简单图论)

[题目概述] 有 N 个瓶子&#xff0c;编号 1∼N&#xff0c;放在架子上。 比如有 5 个瓶子&#xff1a; 2 1 3 5 4 要求每次拿起 2 个瓶子&#xff0c;交换它们的位置。 经过若干次后&#xff0c;使得瓶子的序号为&#xff1a; 1 2 3 4 5 对于这么简单的情况&#xff0c;显然&a…