• React原理剖析之diff算法,一点也不难!!!


    diff算法听起来很深奥。其实真的没那么难。今天我们就用最简单的话把diff算法讲清楚。

    虚拟Dom

    什么是虚拟Dom

    在传统的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"}
    • 1

    vNode这种对象,这玩意就是react中虚拟DOM的真实面目。

    为什么要有虚拟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