前言

今天分享一下我对vue的diff的探究,跟我一起深入底层,看一看vue是怎么进行diff的,它的复杂度如何,为什么性能这么高,diff的目标是尽可能的复用原来的真实dom,减少删除真实dom和创建真实的dom的开销,我们围绕这个目标展开。


diff发生的时刻以及目的

首先我们要明确的是diff发生在什么时候,它是发生在updateComponent的时刻,也就是比对新旧虚拟dom,最小化变化真实dom的过程。


过程深究

现在我们来模拟整个过程,执行完render以后的虚拟dom是一颗树结构,整个过程其实是一个dfs的过程,首先是从跟根节点开始,比对根节点,接着会到下一层节点(注意是一层节点),如果只有一个节点(可能是组件节点或者普通的元素节点,我们都叫节点),那么就会直接比较它们的key和类型,比如他可能是一个组件类型也可能是一个div,如果有一个不一样就会直接把整个子树卸载,然后递归生成正确的真实dom,当如果不是一个节点,那么其实就是两组虚拟dom的比对,这个过程是最关键的:
因为是两个列表,vue会为旧虚拟dom列表创建两个指针,指向头尾,然后同理,新的也是头尾两个指针,然后他会按照:旧列表的头指针和新列表的头比较、旧尾和新尾,如果有发现key一样且类型一样的,直接复用,如果没有匹配上,就会按照旧头和新尾和旧尾新头就进行对比,有的就直接复用,没有的话直接进入一个关键环节diff的精髓所在:
首先对剩下的没有处理的新虚拟dom点进行一个映射,map[虚拟dom节点] = id,这个id其实是它在新虚拟dom节点列表中的下标,然后会遍历旧的dom树,并且用map[虚拟dom节点] = id记录下来。接下来,对旧dom列表进行一个匹配,匹配新dom列表中key和类型匹配,因为索引(map的映射值)是递增的,所以我们会对旧dom列表求一个LIS(最长上升子序列),源码中的话是通过二分+贪心找到的:

<template>
<div class="app"><h2>最长上升子序列</h2>
</div>
</template><script setup lang="ts">
const arr: Array<number> = [10, 30, 2, 1, 5, 5, 7] 
const inf: number = Math.floor(1e9) 
const lastIdx: Array<number> = new Array(arr.length).fill(-1)   
const q: Array<Array<number>> = [[-inf, 0, -1, -1]]
let c: number = 0 
let idx: number = 1 
let maxLen: number = 0 
for(let n of arr) {if(n > q[idx - 1][0]) {maxLen = Math.max(maxLen, q[idx - 1][1] + 1) q[idx] = [n, q[idx - 1][1] + 1, q[idx - 1][3], c]  lastIdx[c] = q[idx-1][3] idx += 1 c += 1continue }else if(n == q[idx - 1][0]) {c += 1 continue; }let l: number = 0, r: number = q.length - 1while(l < r) {let mid: number = Math.floor((l + r + 1) / 2) if(q[mid][0] <= n) l = mid else r = mid - 1  }let [v, le, last, Idx] = q[l] let va = n, curLen = 1, curLast = -1, curIdx = c if(v < n) {maxLen = Math.max(maxLen, le + 1) curLen = le + 1 curLast = Idx }else if(v === n) {curLen = le curLast = last }l = 0, r = q.length - 1 while(l < r) {let mid = Math.floor((l + r) / 2) if(q[mid][0] > n) r = mid else l = mid + 1  }lastIdx[curIdx] = curLast q[l] = [va, curLen, curLast, curIdx]c += 1 
}console.log(arr); 
console.log(maxLen, q);//比如说找最后一个:
let cur = arr.length - 1 
let res = Array<number>() 
while(cur != -1) {res.push(arr[cur]) cur = lastIdx[cur] 
}
res.reverse() 
console.log(res); </script><style scoped></style>

简单的二分+贪心只能够把最长的长度找出来,但是具体的序列找不出来,根据上面的改进能找到具体的序列,源码大概也是差不多实现方式。
因为这一部分其实就可以保持不动了,然后我们将剩下的新dom点拿出。接着,继续diff,从旧dom列表从后往前并且从剩下的新dom列表从后往前看,遇到直接匹配的,直接跳过,如果没有匹配,如果旧dom的map中有,则会通过浏览器的方法insertBeforeinsertBeforeinsertBefore直接将这个真实dom点移动到该点位置前面,如果没有就直接创建然后调用insertBefore方法,关键点来了,当前先保留这个点。然后一直进行,最后才将多余的点清空

总的时间复杂度就是O(n + nlogn) * O(insertBefore)

这里讲几个为什么,也是我在写博客的时候,引发的一些思考:

(1) 为什么不直接全部删除,然后一次性创建,时间复杂度不是O(n)吗,如果涉及真实dom移动不是看起来会更慢吗?
删除和创建dom的效率是极其慢的,但是移动dom是用浏览器底层的方法insertBefore,它是非常高效率的方法,所以我们尽可能避免让dom创建和删除,移动是可以接受的。

(2)为什么最后才将多余的点清空。
因为当我们从后往前遍历的时候,如果我们在当前点操作完了,并且旧点没用到,如果马上删除,并且前面的点可以复用他,但是没复用到,就可能要删除这个点,还要创建一个点,代价多了非常大,所以我们最后清除,可以尽可能的复用。

(3) 为什么不直接删除旧dom列表中不存在于新dom列表中的点?
因为要保留insertBefore的参考位置,保证dom整体结构的稳定性,才会选择最后删除,它们的作用其实是作为参照物的作用,也是vue做出的取舍。

笔者水平有限,不够严谨之处勿喷,多多赐教。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Docker】Docker的初步认识以及Ubuntu下的Docker环境安装、配置

前言 在当今快速迭代的软件开发与部署领域&#xff0c;容器化技术已成为不可或缺的核心力量&#xff0c;而 Docker 作为容器化技术的杰出代表&#xff0c;正以其轻量、高效、可移植的特性深刻改变着开发与运维的模式。它有效解决了 “在我机器上能运行&#xff0c;在你那里却不…

【密码学】2. 古典密码

目录2. 古典密码2.1 经典加密技术基础2.2 代换技术2.2.1 算术密码2.2.2 代换密码&#xff08;Substitution Cipher&#xff09;2.3 置换技术2.4 乘积密码2.5 历史上的教训2. 古典密码 2.1 经典加密技术基础 分类 代换&#xff08;Substitution&#xff09;&#xff1a;明文内…

CSS3文本阴影特效全攻略

CSS3文本阴影效果实现 下面我将创建一个展示各种CSS3文本阴影效果的页面&#xff0c;包含多种样式示例和代码实现。 设计思路 创建具有视觉吸引力的标题区域提供多种文本阴影效果实例显示对应的CSS代码以供参考添加交互元素让用户自定义效果 实现代码 <!DOCTYPE html&g…

JavaScript 03 严格检查模式Strict字符串类型详解

2.4 严格检查模式Strict在 JavaScript 里&#xff0c;也是 有 “作用域” 这个说法的。 所以说&#xff0c;变量 也分 全局变量 和 局部变量。 当我们 直接 把 代码 写在 script 双标签里面的时候&#xff0c;我们 JS 会认为 这只是 一个 没有名字的 函数&#xff01;&#xff…

车载诊断ECU架构

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

使用vue-pdf-embed发现某些文件不显示内容

在使用vue-pdf-embed过程中, 突然发现有些pdf文件可以正常打开, 有些文件只显示了一些数字, 并且控制台报出如下警告: Warning: loadFont - translateFont failed: “UnknownErrorException: Ensure that the cMapUrl and cMapPacked API parameters are provided.”. Warning…

【设计模式C#】状态模式(用于解决解耦多种状态之间的交互)

一种行为设计模式。特点是用类的方式去管理状态。优点&#xff1a;对每个状态进行了封装&#xff0c;提高了代码的可维护性&#xff1b;减少了条件判断语句的使用&#xff0c;降低维护成本&#xff1b;易于扩展&#xff0c;每次新增状态都无需大规模修改其他类&#xff0c;符合…

WebSocket数据通过splice保持现有DOM结构仅更新文本内容‌【防闪烁】。

文章目录 前言 一、DOM更新优化机制 ‌1.虚拟DOM复用性 2.‌响应式系统触发 二、性能对比 三、WebSocket场景实践 ‌1.防闪烁原理 2.代码实现示例 四、特殊注意事项 总结 前言 开发过程中渲染websocket返回的数据时&#xff0c;经常会遇到更新数据闪烁的问题&#xff0c;咱们可…

深入解析Hadoop的Block多副本同步机制与Pipeline复制

Hadoop分布式文件系统概述作为Hadoop生态的核心存储组件&#xff0c;HDFS&#xff08;Hadoop Distributed File System&#xff09;的设计哲学源于Google File System论文&#xff0c;其架构专门针对大规模数据集处理场景进行了优化。在理解Block多副本同步机制之前&#xff0c…

洪水预报中的序列到序列模型及其可解释性扩展

洪水预报中的序列到序列模型及其可解释性扩展 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c;觉得好请收藏。点击跳转到网站。 1. 引言 洪水预报是水文科学和灾害管理中的重要课题&#xff…

UniApp 优化实践:使用常量统一管理本地存储 Key,提升可维护性

在 UniApp 项目开发中&#xff0c;随着功能的增加&#xff0c;本地存储&#xff08;如 uni.setStorageSync&#xff09;的使用频率也会增加。如果直接在代码中硬编码 key 值&#xff0c;不仅容易出错&#xff0c;也难以后期维护。本文将以“自定义导航栏适配状态栏高度”为例&a…

计算机网络:(八)网络层(中)IP层转发分组的过程与网际控制报文协议 ICMP

计算机网络&#xff1a;&#xff08;八&#xff09;网络层&#xff08;中&#xff09;IP层转发分组的过程与网际控制报文协议 ICMP前言一、IP层转发分组的过程第一步&#xff1a;接收数据包并解封装第二步&#xff1a;提取目标 IP 地址第三步&#xff1a;查询路由表第四步&…

Python爬虫实战:研究concurrent-futures库相关技术

1. 引言 1.1 研究背景与意义 网络爬虫作为互联网数据采集的重要工具,在信息检索、舆情分析、学术研究等领域具有广泛应用。随着互联网数据量的爆炸式增长,传统单线程爬虫的效率已难以满足需求,并发爬虫技术成为研究热点。 1.2 相关工作 现有爬虫框架如 Scrapy、Beautifu…

Neo4j 框架 初步简单使用(基础增删改查)

Neo4j 是一个高性能的、开源的图数据库。它将数据存储为图结构&#xff0c;其中节点表示实体&#xff0c;边表示实体之间的关系。这种图数据模型非常适合处理复杂的关系型数据&#xff0c;能够高效地进行关系查询和遍历。 Neo4j 的主要特性包括&#xff1a; 强大的图查询语言 C…

【iOS】锁[特殊字符]

文章目录前言1️⃣什么是锁&#x1f512;&#xff1f;1.1 基本概念1.2 锁的分类2️⃣OC 中的常用锁2.1 OSSpinLock&#xff08;已弃用&#xff09;&#xff1a;“自旋锁”的经典代表为什么尽量在开发中不使用自旋锁自旋锁的本质缺陷&#xff1a;忙等待&#xff08;Busy Waiting…

在easyui中如何设置自带的弹窗,有输入框

这个就是带input的确认弹框&#xff08;$.messager.prompt&#xff09;// 使用prompt并添加placeholder提示 $.messager.prompt(确认, 确定要将事故记录标记为 statusText 吗&#xff1f;, function(r) {if (r) {// r 包含用户输入的内容var remark r.trim();// 验证输入不为…

Android-API调用学习总结

一、Postman检查API接口是否支持1.“HTTP Request” 来创建一个新的请求。——请求构建界面&#xff0c;这是你进行所有 API 调用的地方。2.设置请求方法和 URL&#xff1a;选择请求方法&#xff1a; 在 URL 输入框左侧&#xff0c;有一个下拉菜单。点击它&#xff0c;选择你想…

《计算机网络》实验报告一 常用网络命令

目 录 1、实验目的 2、实验环境 3、实验内容 3.1 ping基本用法 3.2 ifconfig/ipconfig基本用法 3.3 traceroute/tracert基本用法 3.4 arp基本用法 3.5 netstat基本用法 4、实验结果与分析 4.1 ping命令的基本用法 4.2 ifconfig/ipconfig命令的基本用法 4.3 tracer…

MySQL深度理解-深入理解MySQL索引底层数据结构与算法

1.引言在项目中会遇到各种各样的慢查询的问题&#xff0c;对于千万级的表&#xff0c;如果使用比较笨的查询方式&#xff0c;查询一条SQL可能需要几秒甚至几十秒&#xff0c;如果将索引设置的比较合理&#xff0c;可以将查询变得仍然非常快。2.索引的本质索引&#xff1a;帮助M…

Django母婴商城项目实践(九)- 商品列表页模块

9、商品列表页模块 1、业务逻辑 商品模块分为:商品列表页 和 商品详情页 商品列表页将所有商品按照一定的规则排序展示,用于可以从销量、价格、上架时间和收藏数量设置商品的排序方式,并且在商品左侧设置分类列表,选择某一个分类可以筛选出对应的商品信息。 商品列表页…