diff算法听起来很深奥。其实真的没那么难。今天我们就用最简单的话把diff算法讲清楚。
在传统的dom中只有真实Dom↓
这玩意就是真实DOM,我们能看见的节点元素。↓
react/vue这些组件化的框架诞生后,增加了一个概念,虚拟Dom。所以,在传统的浏览器和真实Dom之间增加了一层。虚拟Dom层。成了这样↓
虚拟DOM本质上他就只是一个对象!!! 没什么难的,虚拟DOM就只是一个对象而已。只是这玩意,我们不能在视图上只管的看见,它在源码里。
我们拿一段真实DOM来映射一下与之对应的虚拟Dom↓
//真实DOM我是p元素
//与之对应的虚拟DOM const vNode = {key:"d",//是否有key,有则显示,无则显示nullprops:{ //标签里是否子元素 children:[{type:'p',....}, ], onClick:() => {},//标签上的事件 className:"d-class", //标签上的属性}ref:"null",type:"div"}
vNode这种对象,这玩意就是react中虚拟DOM的真实面目。
因为重新从头生成个正正经经的DOM节点开销实在是太大了!!! 从浏览器开始创建一个节点到这个节点完全渲染到视图上去,是比较耗费性能的,当一群节点都需要去创建并渲染的时候,极端情况下,会出现肉眼可见的卡顿。虚拟Dom的作用就是尽量减少元素的创建。
我们再来一个简单的流程解释一下
// dom结构 一我是p元素
后面需要被改变的标签// dom结构 二我是p元素
节点被更改
- 1
观察上面的dom节点一和dom节点二,不难发现。其实只有div改变成了h1。但就是这一个小小的改动,浏览器重新渲染的话,不用虚拟dom的情况下浏览器会这么做↓
1.删除所有节点
2.重新创建div标签,给div标签赋予class
3.在div下重新创建p标签,给P标签增加class并填充文本
4.在div下重新创建h1标签并填充文本
观察三个步骤,其中最耗费性能的,就是创建节点的过程。
在使用虚拟DOM的情况下。更新视图会有如下操作↓
1.比对结构一和结构二的树结构
2.发现div标签没有变。直接拿来复用(这样就不用删除也不用重新创建了)
3.发现p标签也没有变动,可以直接拿来复用(不用重新删除并创建)
4.发现div标签变成了h1标签,删除原先div标签,重新创建h1标签并插入(只有这一个需要重新生成)
然后就明朗了。
在不使用虚拟DOM的情况下,哪怕改变的只有一个子节点,所有的节点也都需要重新渲染。(我们以前使用JQ的那个时代,就是这么干的)
在使用虚拟DOM的情况下。有很多没有改变的节点(或组件)可以被复用, 直接省掉了好多创建节点的步骤。所以虚拟DOM会带来性能上的提升。
diff算法
很简单,在虚拟DOM中,会有两棵树,一颗修改前的树,一颗修改后的树。
在react中,diff中有三大策略。
diff三大策略
1.Tree diff (树比对)* 比较新旧两棵树,找出哪些节点需要更新* 发现组件则进入 Component diff* 发现节点则进入 Element diff
2.Component diff (组件比对)* 如果节点是组件,比对组件类型* 类型不同直接替换(删除旧组件,创建新组件)* 类型相同则只更新属性* 然后深入组件进行 Tree diff(递归)
3.Element diff (元素比对)* 如果节点是原生标签,则看标签名* 标签名不同直接替换(删除旧元素,创建新元素)* 标签名相同则只更新属性* 然后深入标签做 Tree diff(递归)比对会从Tree diff(树比对开始),扫到组件进入Component diff,扫到元素则进入Elemenet diff。这是一个递归查询的过程。
key是干啥的?这玩意是如何对项目进行优化的。
首先,diff会对新旧两颗树进行同级比较。绝不会出现跨层比较的情况。
在某一层比对发现元素类型不一样之后,自该层往下节点会直接全部删除,不会再往下比对。
更有意思的是↓
当组件树发生上图这样的变化时,从一个人类的思维来说,应该是删除p元素,再把span元素移动到左边。
但机器不是这么理解的!!
在机器的理解上,diff算法会对以上例子做出以下动作
- 比对新旧节点数第一层的div是否被更改,没有改动,进入下一层比较
- 发现旧节点树下第一个元素是p。新节点树下的第一个节点是span (p 比 span)
- 删除p元素,创建span元素。
- 再进行比对,发现旧节点树第二个元素是span,新节点树没有第二个元素(span 比 undefined)
- 删除span元素
也就是说,如果你删除第一个元素,后面所有的元素都会被删除然后重新创建。
基于机器这种笨重的思维方式情况下。
key诞生了。
说白了。key是一种标记。
没有用key的情况下机器默认的算法是按顺序一个一个去比较(注意奥,是按元素顺序去比较)。只要对应顺序上的元素类型对不上,直接删除然后重建。
但是!!用了key则不一样。用了key机器会开启另一种比较算法。
当虚拟dom发现一个节点存在key以后。他就不会按顺序去比较旧节点树中相同位置的那个元素。而是会在旧树的同级元素中遍历寻找该key所标记的节点是否存在。是否可以复用。
不使用key的情况 
如上图所示,在不使用key的情况下。diff算法会根据元素的位置去比对。如果你像上图那样删除了第一个元素的话,后面的元素都会因为比对不成功而全部被删除然后重新创建。
在使用key的情况下
在使用了key的情况下,会寻找匹配新旧节点树中是否有可以复用的元素。这样可以避免不必要的性能浪费。
总结
- 首先,虚拟dom本质上就只是一个js对象而已
- diff算法有三大策略,树比对(Tree diff),组件比对(Component diff),元素比对(Element diff)
- 尽量使用key,在增删元素的时候可以提高性能
- 创建并渲染一个节点的流程相对耗费性能,所以需要虚拟dom来尽量减少节点的重新创建和删除
-
相关阅读:
正则表达式匹配
服务器模拟互联网服务
【笔记篇】11仓管系统WCS系统——之《实战供应链》
java计算机毕业设计基于springboo+vue的学生毕业离校系统
Python精髓之括号家族:方括号、花括号和圆括号,你真的会用吗?
通过Gunicorn、Supervisor和Nginx更好地运行Django
【wpf】wpf踩坑找不到资源.xaml
23.2 Bootstrap 卡片
golang 并发同步(sync 库)-- 单例模式的实现(六)
DDD领域驱动模型笔记
-
原文地址:https://blog.csdn.net/weixin_53312997/article/details/126370759