虚拟 DOM 和 DOM diff

270 阅读4分钟

这篇文章简要阐述一下虚拟 DOM 和 DOM diff 的相关内容。

虚拟 DOM 是什么?

很多时候我们都说 DOM 操作很慢,虚拟 DOM 操作快,但是这需要有对比概念,DOM 操作慢是对比与 JS 原生 API,如数组操作,其实在很多时候,那些基于 DOM 的库(Vue/React)并没有在操作 DOM 时比 DOM 快,只是在某些情况下虚拟 DOM 更快。

简单点,虚拟 DOM 其实就是一个能够代表 DOM 数的对象,通常含有标签名、标签名上的属性、事件监听、子元素们以及其他属性。在 React 中,我们可以通过 JSX 来方便地创建虚拟 DOM,在 Vue 中,我们可以通过 template 模板语法来形象地创建虚拟 DOM ,如下所示:

在 React 中:

<div className = "red" onClick = {fn}>
    <span>span1</span>
    <span>span1</span>
</div>

在 Vue 中:

<div className = "red" @click = "fn">
    <span>span1</span>
    <span>span1</span>
</div>

虚拟 DOM 的优点

虚拟 DOM 有两个很大的优点:

1、减少不必要的 DOM 操作,从两个方面来表现,一方面,虚拟 DOM 可以减少 DOM 操作次数,例如当我们要在页面中添加多个节点的时候,就可以利用虚拟 DOM 将多次操作合并为一次操作,而不需要一个接一个操作;另一方面,虚拟 DOM 能够减少操作范围,因为可以借助 DOM diff 可以把多余的操作省掉,当我们要添加多个节点时,其实只有几个需要更新的话,那么借助 DOM diff 就可以只更新发生了变化的节点。

2、虚拟 DOM 能够实现跨平台,因为其本质上就是一个对象,我们不仅可以将其变成 DOM,还可以变成小程序、IOS应用、安卓应用等。

虚拟 DOM 的缺点

其实上述在 React 中创建虚拟 DOM 时本质上使用的是 React 中的 createElement 函数,在 Vue 中本质上使用的是 h 函数,所以虚拟 DOM 总是需要额外的创建函数,虽然我们可以像上述那些简写成 XML 写法,但这样就十分依赖打包工具了,没有打包工具进行转译的话,代码也就不会顺利运行了。

DOM diff 是什么

谈到虚拟 DOM 的优点时提到可以借助 DOM diff 将多余的操作省掉,那么 DOM diff 是什么呢?这是虚拟 DOM 的一种对比算法,当数据发生变化时,它会去对比前后的 DOM ,查找哪些地方发生了变化,然后返回需要改变的操作,其实也就是一个函数,通常称之为 patch,其中可能的大概逻辑是:

1.首先是做 Tree diff,将新旧两棵树逐层对比,找出哪些节点需要更新,如果节点是组件就看 Component diff,如果节点是标签就看 Element diff。

2.在Component diff中,如果节点是组件,就先看组件类型,类型不同直接替换(删除旧的),类型相同则只更新属性,然后深入组件做 Tree diff(递归)。

3.在Element diff中,如果节点是原生标签,则看标签名,标签名不同直接替换,相同则只更新属性,然后进入标签后代做 Tree diff(递归)。

DOM diff 的特点

从上面的描述中可知,通过使用 DOM diff,我们可以只更新发生了改变的节点,而不必每次更新所有的节点,但是 DOM diff 也是存在一定问题的,那就是在同级节点中对比会有一些 bug,会出现识别错误的问题,因为计算机总是会从左到右、从上到下进行计算,假设现在有三个同级节点,当我们删除了第二个节点后,在 DOM diff 算法中,其实并不是认为删除了第二个节点,而是认为原本的第二个节点发生了改变,变成了第三个节点的内容,而第三个节点则被认为是删除了,那么怎么避免这个问题呢?这就需要给每一个节点加上一个唯一的属性 key,来标识每个节点,这要就可以避免识别错误,也就是为什么在 vue 中的 v-for 里必须要声明 :key。

更多内容可以阅读:

React 虚拟 Dom 和 diff 算法

React 源码剖析系列 - 不可思议的 react diff