React学习笔记(八) 虚拟DOM与Diff算法
1、虚拟 DOM
(1)什么是虚拟 DOM
我们先来回顾一下什么是 DOM?DOM 是一个 用于表示 HTML 文档结构 的树,实际上它是一个 JavaScript 对象
树上的每一个节点代表一个 HTML 元素,每个 HTML 元素拥有大量的属性、方法和事件,是一个十分复杂的对象
所谓的虚拟 DOM 就是 用于表示真实 DOM 结构 的一个 JavaScript 对象,它与真实 DOM 之间存在一个 映射关系
每个 React 元素对应一个 HTML 元素,但是 React 元素只保留着一些必要的属性,所以相对而言修改开销较小
(2)为什么使用虚拟 DOM
当状态发生变化时,React 不会直接更新视图(其目的是为了减少多次操作实际 DOM)
而是创建一个新的虚拟 DOM 缓存 所有发生变化的状态,然后等到合适的时候 一次性 更新到浏览器
当组件重新渲染时,React 不会渲染整个视图(其目的是为了减少大量操作实际 DOM)
而是使用 Diff 算法 比较新旧虚拟 DOM 之间的差异,然后只把 真正发生变化的地方 更新到浏览器
用一句话概括,使用虚拟 DOM 的目的就是 提高性能,它的核心其实就是 减少实际的 DOM 操作
(3)怎么样使用虚拟 DOM
虚拟 DOM 是 React 实现的内部机制,它的运作过程可以粗略概括如下:
- 使用 JavaScript 对象创建一棵虚拟 DOM 树,然后通过虚拟 DOM 树构建一棵真实 DOM 树,插入到文档中
- 当状态发生变化时,重新构造一棵虚拟 DOM 树,比较新旧两棵虚拟 DOM 树之间的差异
- 将步骤 2 中的差异部分更新到步骤 1 中的真实 DOM 树
2、Diff 算法
Diff 算法用于比较两棵树之间的差异,虚拟 DOM 之所以能极大提高性能,离不开其高效的 Diff 算法
传统的 Diff 算法通过递归遍历树的节点比较差异,算法复杂度为 O(n^3),性能是十分堪忧的
但是 React 的 Diff 算法可以达到 O(n),这种极大的提升完全得益于 React 的 三大 Diff 策略,下面逐一进行学习
(1)tree diff
依据:Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
两棵树只会对 同一层级 的节点进行比较,具体来说就是 依次比较相同父节点下的所有子节点
-
如果节点相同,那么保留下来
-
如果发现有节点消失,那么直接删除该节点及其子节点,不再往下进行比较
-
如果发现有节点新增,那么创建新的节点,然后继续往下创建其子节点
这样只要遍历一次树的节点就能完成比较,瞬间将算法复杂度降至 O(n),这里举一个例子:
-
比较节点 A 的子节点 B,发现 B 消失(差异部分),所以删除节点 B 及其子节点 D 和 E
-
比较节点 A 的子节点 C,发现 C 相同(没有差异),保留
-
比较节点 C 的子节点 B,发现 B 新增(差异部分),所以创建节点 B 及其子节点 D 和 E
(2)component diff
依据:相同类型的组件会有相似的树形结构,不同类型的组件会有不同的树形结构
React 是基于组件构建应用的,它会 以粗于层级的粒度(组件) 进行比较,这里分为两种情况
-
如果是同一类型的组件,按照 tree diff 策略继续比较
-
如果是不同类型的组件,不会继续比较,而是直接删除,然后重新渲染组件
即使两个组件的结构十分相似,如果被判断为不同类型,也会销毁重建,这里举一个例子:
(3)element diff
依据:对于同一层级的一组节点,可以通过唯一的 key 进行区分
对于同一层级的节点,将会 以精于层级的粒度(元素) 进行比较,React 提供三种操作
- 插入:新节点不存在于旧集合中,这样就会插入新的节点,作出插入操作
- 移动:新节点存在于旧集合中,且新节点只是位置有所变化,这样就会复用旧的节点,作出移动操作
- 删除:新节点存在于旧集合中,但新节点有所更改不能复用,这样就会删除旧的节点,作出删除操作
伪代码如下:
oldNodes <- 旧节点集合
newNodes <- 新节点集合
lastIndex <- 0
for item in newNodes:
if item in oldNodes:
index <- index of item in oldNodes
if index < lastIndex:
在 oldNodes 中,将 item 移动到 lastIndex 位置后,相应元素前移一位
lastIndex = max(index, lastIndex)
else:
在 oldNodes 中,创建 item 并将 item 插入到 lastIndex 位置后,相应元素后移一位
lastIndex = lastIndex + 1
for item in oldNodes:
if item not in newNodes:
在 oldNodes 中,删除 item
例子如下:
【 阅读更多 React 系列文章,请看 React学习笔记 】