【Vue2.0源码学习】虚拟DOM篇-Vue中的DOM-更新子节点

news2025/1/11 6:10:55

文章目录

    • 1. 前言
    • 2. 更新子节点
    • 3. 创建子节点
    • 4. 删除子节点
    • 5. 更新子节点
    • 6. 移动子节点
    • 7. 回到源码
    • 8. 总结

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/582432.html

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

相关文章

小型流水线模型的制作

1. 功能说明 本文示例将实现R327a样机——一款5工序的小型流水线模型&#xff0c;包含铸锭送料、传送、搬运、模拟加工、码垛5个工序。 2. 结构说明 小型流水线主要是由铸锭送料结构、传送机构、搬运机构、模拟加工机构、码垛机构5部分组成。 3. 电子硬件 在这个示例中&#xf…

Qt文件系统源码分析—第七篇QFileSelector

深度 本文主要分析Windows平台&#xff0c;Mac、Linux暂不涉及 本文只分析到Win32 API/Windows Com组件/STL库函数层次&#xff0c;再下层代码不做探究 本文QT版本5.15.2 类关系图 QTemporaryFile继承QFile QFile、QSaveFile继承QFileDevice QFileDevice继承QIODevice Q…

【SpringMVC】| 一文带你搞定SpringMVC的@RequestMapping注解

目录 环境搭建 Request注解的功能 1. RequestMapping注解的位置 2、RequestMapping注解的【value】属性 3、RequestMapping注解的【method】属性 4、RequestMapping注解的【params】属性&#xff08;了解&#xff09; 5、RequestMapping注解的【headers】属性&#xff0…

Attention 和 Transformer

本文参考d2l&#xff0c;搭配知识点和代码&#xff0c;助力一口气搞懂Transformer&#xff0c;参考&#xff1a; chapter_attention-mechanisms-and-transformers https://d2l.ai/chapter_attention-mechanisms-and-transformers/index.html 目录如下&#xff1a; x.1 Queri…

项目复盘四步:怎么做才有成效?这些关键点不可忽略

在项目管理中&#xff0c;及时复盘是非常重要的&#xff0c;因为只有通过反思和分析&#xff0c;才能找到差距存在的原因。 复盘分析的第一步是回顾目标 因为目标是工作开展的关键。在执行项目的过程中&#xff0c;要始终朝着所设定的目标去努力实现。计划和现实会存在偏差&…

超详细Zookeeper+Kafka+ELK集群部署

一、Zookeeper 概述 1、Zookeeper 定义 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 2、Zookeeper 工作机制 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理…

AutoCV第九课:ML基础

目录 矩阵运算前言1. 矩阵乘法和求导总结 矩阵运算 前言 手写AI推出的全新保姆级从零手写自动驾驶CV课程&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课程主要学习矩阵运算的基础&#xff0c;考虑使用矩阵来表达多个线性回归模型。 课程大纲可看下面…

多元回归预测 | Matlab龙格算法(RUN)优化最小二乘支持向量机回归预测,RUN-LSSVM回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab龙格算法(RUN)优化最小二乘支持向量机回归预测,RUN-LSSVM回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %--…

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现

Squeeze-and-Excitation Networks&#xff08;压缩和激励网络&#xff09; 论文地址&#xff1a;Squeeze-and-Excitation Networks 论文中文版&#xff1a;Squeeze-and-Excitation Networks_中文版 代码地址&#xff1a;GitHub - hujie-frank/SENet: Squeeze-and-Excitation Ne…

git 文件恢复与项目还原:008

1. 【文件恢复】&#xff1a;将文件恢复到上一次提交的状态 注意&#xff1a;新建且没有提交的文件无法使用文件恢复 命令&#xff1a; git checkout -- 文件名假如我们的一开始是这样的&#xff0c;这是没有报错的状态文件 然后我添加了一段内容&#xff0c; 比如我添加这段内…

做外贸算运费的时候需不需要多算一些

看到一个网友在一篇文章下留言说&#xff1a;客户算运费的时候需不需要多算一些 听公司老员工说给客户算运费要多加20% 这样合适吗 我个人感觉有点离谱。 那我们就这个话题&#xff0c;谈一谈运费是否要多加一些呢&#xff1f;为什么要多加一些&#xff1f; 首先&#xff0c;要…

Zookeeper学习---3、服务器动态上下线监听案例、ZooKeeper 分布式锁案例、企业面试真题

1、服务器动态上下线监听案例 1、需求 某分布式系统中&#xff0c;主节点可以有多台&#xff0c;可以动态上下线&#xff0c;任意一台客户端都能实时感知到主节点服务器的上下线。 2、需求分析 3、具体实现 &#xff08;1&#xff09;先在集群上创建/servers 节点 &#xff…

软考A计划-试题模拟含答案解析-卷八

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

2023年上半年系统集成项目管理工程师下午真题及答案解析

试题一(18分) A公司跨国收购了B公司的主营业务&#xff0c;保留了B公司原有的人员组织结构和内部办公系统。为了解决B公司内部办公系统与A公司原有系统不兼容的问题&#xff0c;财务、人力和行政部门联合向公司高层申请尽快启动系统和业务的整合。 A公司领导指定HR总监王工担…

云容灾部署前的准备指南

据ITIC的研究表明&#xff0c;98%的千人规模企业每年都会遭遇停机危机&#xff0c;每停机一小时就会损失约700,000人民币。当灾难发生时&#xff0c;使用云容灾的企业可以通过云平台提供的资源和服务&#xff0c;快速帮助企业恢复业务。 HyperBDR云容灾&#xff0c;深度对接全…

Kibana:使用 Docker 安装 Kibana - 8.x

Kibana 的 Docker 镜像可从 Elastic Docker 注册中心获得。 基本映像是 ubuntu:20.04。www.docker.elastic.co 上提供了所有已发布的 Docker 图像和标签的列表。 源代码在 GitHub 中。 这些镜像包含免费和订阅功能。 开始 30 天试用以试用所有功能。 如果你还没有安装好自己的…

一文了解什么是ChatGPT

ChatGPT 是一种自然语言人工智能聊天机器人。在最基本的层面上&#xff0c;这意味着你可以问它任何问题&#xff0c;它会生成一个答案。 一、如何使用聊天 GPT 首先&#xff0c;转到chat.openai.com。如果这是您的第一次&#xff0c;您需要在开始之前使用 OpenAI 设置一个免费…

C919中有哪些项目是华为之作?

#C919# C919和华为都是我们国人的骄傲。那你知道在C919中有哪些项目是华为之作吗&#xff1f;C919与华为的合作主要涉及航空电子领域&#xff1a; 1.飞机高清视频传输系统&#xff1a;该系统使用华为的数字视频传输技术&#xff0c;可以将高清视频信号快速地传输到地面监控中心…

Gradio的web界面演示与交互机器学习模型,高级接口特征《6》

大多数模型都是黑盒&#xff0c;其内部逻辑对最终用户是隐藏的。为了鼓励透明度&#xff0c;我们通过简单地将Interface类中的interpretation关键字设置为default&#xff0c;使得向模型添加解释变得非常容易。这允许您的用户了解输入的哪些部分负责输出。 1、Interpret解释 …

NetApp E 系列混合闪存阵列——专为需要高带宽的专用应用程序而构建(如数据分析、视频监控、HPC、基于磁盘的备份)

E 系列混合闪存阵列&#xff1a;专为交付而构建 为什么选择 NetApp E 系列阵列&#xff1f; 超过 100 万次的安装和计数 凭借其提供的精简性和可靠性&#xff0c;我们的 E 系列阵列成为了众多企业的首选系统。从推动数据密集型应用程序&#xff08;如分析、视频监控和基于磁盘…