一、虚拟DOM

  • DOM的本质是什么:浏览器中的概念,用JS对象来表示 页面上的元素,并提供了操作 DOM 对象的API;
  • 什么是React中的虚拟DOM:是框架中的概念,是程序员用JS对象来模拟页面上的 DOM 和 DOM嵌套;
  • 为什么要实现虚拟DOM(虚拟DOM的目的):为了实现页面中DOM 元素的高效更新
  • DOM和虚拟DOM的区别虚拟DOM的概念.png

    1552877849641.png

    二、react的Diff算法

    参考文档
    什么是Diff算法?
    **二、核心概念 - 图4

  • 传统Diff:diff算法即差异查找算法;对于Html DOM结构即为tree的差异查找算法;而对于计算两颗树的差异时间复杂度为O(n^3),显然成本太高,React不可能采用这种传统算法;

  • React Diff:
    • 之前说过,React采用虚拟DOM技术实现对真实DOM的映射,即React Diff算法的差异查找实质是对两个JavaScript对象的差异查找;
    • 基于三个策略:
    1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。(tree diff)
    2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结(component diff)
    3. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。(element diff)

Diff.png

  • 其实传统diff算法就是对每个节点一一对比,循环遍历所有的子节点,然后判断子节点的更新状态。通过循环递归对节点进行依次对比,算法时间复杂度达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——如果要展示1000个节点,得执行上亿次比较。即便是CPU快能执行30亿条命令,也很难在一秒内计算出差异。。
  • React 通过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题。react根据自己的特点,实现了部分代码的简化。1552878167661.png

    2-1 tree diff

    (1)React通过updateDepth对Virtual DOM树进行层级控制。
    (2)对树分层比较,两棵树只对同一层次节点 进行比较。如果该节点不存在时,则该节点及其子节点会被完全删 除,不会再进一步比较。
    (3)只需遍历一次就能完成整棵DOM树的比较。 1562723697592.png
    那么问题来了,如果DOM节点出现了跨层级操作,diff会咋办呢?
    答:diff只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。1562723819718.png
    如上图所示,以A为根节点的整棵树会被重新创建,而不是移动,因此 官方建议不要进行DOM节点跨层级操作,可以通过CSS隐藏、显示节点,而不是真正地移除、添加DOM节点

2-2 component diff

React应用是基于组件构建的,对于组件的比较优化侧重于以下几点:

  1. 同一类型组件遵从tree diff比较v-dom树
  2. 不同类型组件,先将该组件归类为dirty component,替换下整个组件下的所有子节点
  3. 同一类型组件Virtual Dom没有变化,React允许开发者使用shouldComponentUpdate()来判断该组件是否进行diff,运用得当可以节省diff计算时间,提升性能1562723945651.png

如上图,当组件D → 组件G时,diff判断为不同类型的组件,虽然它们的结构相似甚至一样,diff仍然不会比较二者结构,会直接销毁D及其子节点,然后新建一个G相关的子tree,这显然会影响性能,官方虽然认定这种情况极少出现,但是开发中的这种现象造成的影响是非常大的。

2-3 element diff

对于同一层级的element节点,diff提供了以下3种节点操作:

  1. INSERT_MARKUP 插入节点
  2. MOVE_EXISING 移动节点
  3. REMOVE_NODE 移除节点

    1. ![1562724094202.png](https://cdn.nlark.com/yuque/0/2020/png/402644/1583315235376-6fe81c48-fd50-480d-a6fe-808388b36af5.png#align=left&display=inline&height=243&name=1562724094202.png&originHeight=243&originWidth=522&size=56451&status=done&style=none&width=522)<br />一般diff在比较集合[A,B,C,D]和[B,A,D,C]的时候会进行全部对比,即按对应位置逐个比较,发现每个位置对应的元素都有所更新,则把旧集合全部移除,替换成新的集合,如上图,但是这样的操作在React中显然是复杂、低效、影响性能的操作,因为新集合中所有的元素都可以进行复用,无需删除重新创建,耗费性能和内存,只需要移动元素位置即可。

React对这一现象做出了一个高效的策略:允许开发者对同一层级的同组子节点添加唯一key值进行区分。意义就是代码上的一小步,性能上的一大步,甚至是翻天覆地的变化!