虚拟dom的diff中的双端比较算法

news2025/3/12 22:17:08

‌双端比较算法是Vue中用于高效比较新旧VNode子节点的一种策略‌。该算法的核心思想是,通过从新旧VNode子节点的两端开始比较,逐步向中间靠拢,以找到最小的差异并据此更新DOM。以下是双端比较算法的大致流程:

  • ‌初始化指针‌:设置四个指针,分别指向新旧VNode子节点的开始和结束位置。
  • 首尾比较‌:首先比较新旧VNode子节点的首尾元素。如果首尾元素相同,则直接复用,并移动相应的指针。
  • 交叉比较‌:如果首尾元素不同,则进行交叉比较,即比较旧节点的末尾和新节点的开头,以及旧节点的开头和新节点的末尾。
  • 移动指针‌:根据比较结果,移动指针以缩小比较范围。如果找到匹配的节点,则复用该节点,并根据匹配情况调整指针位置。
  • 结束条件‌:当任一节点的开始指针超过结束指针时,表明已经遍历完至少一个节点的所有子节点,此时结束比较。
    在这里插入图片描述

这里以伪代码的形式进行展示,其中oldChildren是旧的虚拟DOM,newChildren是新的虚拟DOM,通过patch来更新真实的DOM结构

function diff(oldChildren, newChildren) {
    let oldStartIdx = 0              // 旧子节点起始指针
    let oldEndIdx = oldChildren.length - 1  // 旧子节点结束指针
    let newStartIdx = 0              // 新子节点起始指针
    let newEndIdx = newChildren.length - 1  // 新子节点结束指针

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        // 获取当前指针指向的节点
        let oldStartVNode = oldChildren[oldStartIdx]
        let oldEndVNode = oldChildren[oldEndIdx]
        let newStartVNode = newChildren[newStartIdx]
        let newEndVNode = newChildren[newEndIdx]

        // --- 四种比较场景 ---
        // 1. 旧头 vs 新头(复用)
        if (oldStartVNode.key === newStartVNode.key) {
            patch(oldStartVNode, newStartVNode)  // 更新节点属性
            oldStartIdx++
            newStartIdx++
        }
        // 2. 旧尾 vs 新尾(复用)
        else if (oldEndVNode.key === newEndVNode.key) {
            patch(oldEndVNode, newEndVNode)
            oldEndIdx--
            newEndIdx--
        }
        // 3. 旧头 vs 新尾(移动:旧头移动到旧尾之后)
        else if (oldStartVNode.key === newEndVNode.key) {
            patch(oldStartVNode, newEndVNode)
            insert(oldStartVNode.el, parentEl, oldEndVNode.el.nextSibling)  // DOM移动操作
            oldStartIdx++
            newEndIdx--
        }
        // 4. 旧尾 vs 新头(移动:旧尾移动到旧头之前)
        else if (oldEndVNode.key === newStartVNode.key) {
            patch(oldEndVNode, newStartVNode)
            insert(oldEndVNode.el, parentEl, oldStartVNode.el)
            oldEndIdx--
            newStartIdx++
        }
        // --- 如果四种场景都不匹配 ---
        else {
            // 尝试在旧子节点中查找与 newStartVNode 匹配的节点
            let foundIdx = oldChildren.findIndex(node => node.key === newStartVNode.key)
            if (foundIdx !== -1) {
                // 找到可复用的旧节点,移动它到旧头之前
                let foundVNode = oldChildren[foundIdx]
                patch(foundVNode, newStartVNode)
                insert(foundVNode.el, parentEl, oldStartVNode.el)
                oldChildren[foundIdx] = undefined  // 标记该位置已处理
            } else {
                // 没有可复用节点,创建新节点插入到旧头之前
                createEl(newStartVNode)
                insert(newStartVNode.el, parentEl, oldStartVNode.el)
            }
            newStartIdx++
        }
    }

    // --- 处理剩余节点 ---
    // 1. 如果旧子节点遍历完毕,剩余新节点需要新增
    if (oldStartIdx > oldEndIdx && newStartIdx <= newEndIdx) {
        for (let i = newStartIdx; i <= newEndIdx; i++) {
            createEl(newChildren[i])
            insert(newChildren[i].el, parentEl, oldChildren[oldStartIdx]?.el || null)
        }
    }
    // 2. 如果新子节点遍历完毕,剩余旧节点需要删除
    else if (newStartIdx > newEndIdx && oldStartIdx <= oldEndIdx) {
        for (let i = oldStartIdx; i <= oldEndIdx; i++) {
            remove(oldChildren[i].el)
        }
    }
}

其中patch函数的核心作用是 ‌复用已有的真实 DOM 节点,并用新虚拟节点(VNode)的属性更新对应的真实 DOM

问题1:patch 函数的具体更新逻辑
‌(1)更新属性(props)‌
‌新增/修改属性‌:将新 VNode 的 class、style、id、自定义属性等同步到真实 DOM。
‌删除旧属性‌:移除新 VNode 中不存在的旧属性。

// 伪代码示例:更新 class
if (newVNode.class !== oldVNode.class) {
  el.className = newVNode.class;
}

‌(2)更新事件监听器‌
‌绑定新事件‌:若新 VNode 有事件(如 @click),则绑定到真实 DOM。
‌解绑旧事件‌:若旧事件不存在于新 VNode 中,则移除。

// 伪代码示例:更新事件
if (newVNode.onClick !== oldVNode.onClick) {
  el.removeEventListener('click', oldVNode.onClick);
  el.addEventListener('click', newVNode.onClick);
}

‌(3)更新子节点‌
递归对比子节点,触发子树的 Diff 算法:

// 伪代码:递归更新子节点
if (newVNode.children && oldVNode.children) {
  updateChildren(el, oldVNode.children, newVNode.children);
}

‌(4)更新文本内容‌
若节点是文本节点,直接更新文本:

if (newVNode.text !== oldVNode.text) {
  el.textContent = newVNode.text;
}

示例说明‌
假设新旧虚拟节点如下:

// 旧虚拟节点
oldVNode = {
  tag: 'div',
  class: 'old-class',
  onClick: oldHandler,
  children: [A, B]
};

// 新虚拟节点
newVNode = {
  tag: 'div',
  class: 'new-class',
  onClick: newHandler,
  children: [B, A, C]
};

‌patch 函数的操作步骤‌:

  • ‌更新 class‌:将真实 DOM 的 class 从 old-class 改为 new-class。
  • ‌更新事件‌:解绑旧的oldHandler,绑定新的 newHandler。
  • ‌更新子节点‌:触发子节点的双端 Diff 算法,复用 B 和 A,新增 C,可能移动节点位置。

问题:‌在更新过程中旧节点数量是否变化?‌
‌旧虚拟节点(oldChildren)的数量不会改变‌,但‌真实 DOM 中子节点数量会增加‌:

‌(1)新旧虚拟节点的数量是固定的‌
‌旧节点列表(oldChildren)‌:在 Diff 过程中是固定的,长度由初始渲染时决定。
‌新节点列表(newChildren)‌:是目标结构,算法需将真实 DOM 更新到该结构。
无论是否插入新节点,oldChildren 和 newChildren 的虚拟节点数量是固定的,不会动态增减。

‌(2)真实 DOM 的子节点数量会暂时增加‌
‌插入新节点的本质‌:在真实 DOM 中新增了一个节点(newStartVNode.el)。
‌旧节点的存在性‌:未被复用的旧节点仍然存在于真实 DOM 中,直到被明确删除。
此时真实 DOM 的子节点数量为:
旧节点数量 + 新增节点数量 - 已删除的旧节点数量。

‌2. 示例场景
假设初始状态为(这里是虚拟DOM):

oldChildren: [A, B, C]  // 旧节点数量为 3
newChildren: [D, A, B]  // 新节点数量为 3

执行流程如下:

  • ‌处理新头节点 D‌:
  • 在 oldChildren 中未找到可复用的节点。 ‌创建新节点 D‌,并插入到旧头节点 A 之前。
  • 此时真实 DOM 结构为 [D, A, B, C](4 个节点)。

‌继续 Diff 后续节点‌:

  • 新头指针后移(newStartIdx++),处理下一个新节点 A。 发现 A 可复用旧头节点,移动并更新指针。
  • 最终清理未处理的旧节点‌: 新节点处理完毕后,剩余的旧节点 C 会被删除。 ‌最终真实 DOM 结构‌:[D, A, B](与新节点数量一致)。

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

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

相关文章

VMware安装Windows server 2016

1、新建虚拟机&#xff0c;选择自定义模式 2、选择兼容性 4、命名虚拟机 5、固件类型 EFI 虚拟磁盘类型&#xff0c;不同电脑推荐的类型不同&#xff0c;用默认的就行 删除声卡和打印机 检查网络配置 选择本地的Windows server 2016的系统镜像&#xff0c;系统镜像可以去Window…

HippoRAG 2 原理精读

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 整体流程离线索引阶段在线检索和问答阶段 总结 整体流程 从上图可以看出&#xff0c;整个流程分为两个阶段 1、离线索引阶段 2、在线检索和问答阶段 离线索引阶段…

三:FFMPEG拉流读取模块的讲解

FFMPEG拉流读取模块在远程监控项目最核心的作用是读取UVC摄像头传输的H264码流&#xff0c;并对其码流进行帧的提取&#xff0c;提取完成之后则把数据传输到VDEC解码模块进行解码。而在我们这个项目中&#xff0c;UVC推流的功能由FFMPEG的命令完成。 FFMPEG拉流读取模块的API…

《苍穹外卖》SpringBoot后端开发项目核心知识点与常见问题整理(DAY1 to DAY3)

目录 一、在本地部署并启动Nginx服务1. 解压Nginx压缩包2. 启动Nginx服务3. 验证Nginx是否启动成功&#xff1a; 二、导入接口文档1. 黑马程序员提供的YApi平台2. YApi Pro平台3. 推荐工具&#xff1a;Apifox 三、Swagger1. 常用注解1.1 Api与ApiModel1.2 ApiModelProperty与Ap…

QT系列教程(20) Qt 项目视图便捷类

视频连接 https://www.bilibili.com/video/BV1XY41127t3/?vd_source8be9e83424c2ed2c9b2a3ed1d01385e9 Qt项目视图便捷类 Qt项目视图提供了一些便捷类&#xff0c;包括QListWidget, QTableWidget&#xff0c; QTreeWidget等。我们分别介绍这几个便捷类。 我们先创建一个Qt …

动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南

目录 一、典型案例&#xff1a;系统发布后的GC雪崩事件 &#xff08;一&#xff09;故障现象 1. 刚刚启动时 GC 次数较多 2. 堆内存锯齿状波动 3. GC日志特征&#xff1a;Allocation Failure &#xff08;二&#xff09;问题定位 二、原理深度解析&#xff1a;JVM内存弹…

AI智能眼镜主控芯片:技术演进与产业生态的深度解析

一、AI智能眼镜的技术挑战与主控芯片核心诉求 AI智能眼镜作为XR&#xff08;扩展现实&#xff09;技术的代表产品&#xff0c;其核心矛盾在于性能、功耗与体积的三角平衡。主控芯片作为设备的“大脑”&#xff0c;需在有限空间内实现复杂计算、多模态交互与全天候续航&#xf…

微服务拆分-远程调用

我们在查询购物车列表的时候&#xff0c;它有一个需求&#xff0c;就是不仅仅要查出购物车当中的这些商品信息&#xff0c;同时还要去查到购物车当中这些商品的最新的价格和状态信息&#xff0c;跟购物车当中的快照进行一个对比&#xff0c;从而去提醒用户。 现在我们已经做了服…

[网络爬虫] 动态网页抓取 — Selenium 介绍 环境配置

&#x1f31f;想系统化学习爬虫技术&#xff1f;看看这个&#xff1a;[数据抓取] Python 网络爬虫 - 学习手册-CSDN博客 0x01&#xff1a;Selenium 工具介绍 Selenium 是一个开源的便携式自动化测试工具。它最初是为网站自动化测试而开发的&#xff0c;类似于我们玩游戏用的按…

【RAGFlow】windows本地pycharm运行

原因 由于官方只提供了docker部署&#xff0c;基于开源代码需要实现自己内部得逻辑&#xff0c;所以需要本地pycharm能访问&#xff0c;且docker运行依赖得其余组件&#xff0c;均需要使用开发服务器得配置。 修改过程 安装python 项目依赖于Python 版本&#xff1a;>3.1…

树莓派5首次开机保姆级教程(无显示器通过VNC连接树莓派桌面)

第一次开机详细步骤 步骤一&#xff1a;树莓派系统烧录1 搜索打开烧录软件“Raspberry Pi Imager”2 选择合适的设备、系统、SD卡3 烧录配置选项 步骤二&#xff1a;SSH远程树莓派1 树莓派插电2 网络连接&#xff08;有线或无线&#xff09;3 确定树莓派IP地址 步骤三&#xff…

html-表格标签

一、表格标签 1. 表格的主要作用 表格主要用于显示&#xff64;展示数据,因为它可以让数据显示的非常的规整,可读性非常好&#xff61;特别是后台展示数据 的时候,能够熟练运用表格就显得很重要&#xff61;一个清爽简约的表格能够把繁杂的数据表现得很有条理&#xff61; 总…

大模型安全新范式:DeepSeek一体机内容安全卫士发布

2月以来&#xff0c;DeepSeek一体机几乎成为了政企市场AI消费的最强热点。 通过一体机的方式能够缩短大模型部署周期&#xff0c;深度结合业务场景&#xff0c;降低中小企业对于大模型的使用门槛。据不完全统计&#xff0c;已约有超过60家企业基于DeepSeek推出一体机产品。 但…

数据分析绘制随时间顺序变化图加入线性趋势线——numpy库的polyfit计算一次多项式拟合

import pandas as pd import numpy as np import matplotlib.pyplot as plt# 导入数据 data pd.read_csv(rC:\Users\11712\notebooktrain1.csv)# 假设数据包含 date_time 和 speed 列 data[date_time] pd.to_datetime(data[date_time]) # 确保时间列是 datetime 类型 data.s…

密闭空间可燃气体监测终端:守护城市命脉,智驭燃气安全!

近年来&#xff0c;陕西省高度重视燃气安全&#xff0c;出台了一系列政策文件&#xff0c;旨在全面加强城镇燃气安全监管&#xff0c;防范化解重大安全风险。2023年&#xff0c;陕西省安委会印发《全省城镇燃气安全专项整治工作方案》&#xff0c;明确要求聚焦燃气经营、输送配…

阿里千问大模型(Qwen2.5-VL-7B-Instruct)部署

参考链接 知乎帖子 B站视频 huggingface 镜像网站&#xff08;不太全&#xff0c;比如 Qwen/Qwen2.5-VL-7B-Instruct就没有&#xff09; huggingface 5种下载方式汇总 通过huggingface-cli下载模型 不一样的部分是预训练权重的下载和demo 首先安装huggingface_hub pip insta…

【Go学习实战】03-3-文章评论及写文章

【Go学习实战】03-3-文章评论及写文章 文章评论注册valine获取凭证加载评论页面 写文章修改cdn位置完善功能查看页面 发布文章POST发布文章发布文章测试 查询文章详情查询详情测试 修改文章修改文章测试 写文章图片上传前端后端逻辑测试 文章评论 这里我们的博客因为是个轻量级…

从零开始用AI开发游戏(一)

1. 核心玩法设计 核心目标&#xff1a;玩家需在随机生成的3D迷宫中寻找出口&#xff0c;躲避陷阱、收集道具、解开谜题。核心机制&#xff1a; 随机生成迷宫&#xff1a;每次游戏生成不同结构的迷宫&#xff08;递归分割算法或深度优先搜索&#xff09;。第一人称视角&#xf…

AI-大模型中的流式输出与非流式输出

1.前言 在大模型API开发中&#xff0c;流式与非流式输出对应着两种不同的数据交互&#xff0c;在代码中stream中通过参数true与false来进行设定。 2.流式输出与非流式输出的原理 2.1.非流式输出-请求一次响应返回完整数据 非流式输出&#xff0c;传统的请求-响应模式&#xf…

【HarmonyOS Next】鸿蒙加固方案调研和分析

【HarmonyOS Next】鸿蒙加固方案调研和分析 一、前言 根据鸿蒙应用的上架流程&#xff0c;本地构建app文件后&#xff0c;上架到AGC平台&#xff0c;平台会进行解析。根据鸿蒙系统的特殊设置&#xff0c;仿照IOS的生态闭环方案。只能从AGC应用市场下载app进行安装。这样的流程…