有关于 DOM 操作慢的谣言,事实上 任何基于 DOM 的库都不可能再操作 DOM 时比 DOM 快,DOM 操作慢是相比较于 js 原生API而言的。在处理规模适中的节点时,虚拟 DOM 会比 DOM 操作快。
那么虚拟 DOM 是什么?
虚拟 DOM
- 一个能代表 DOM 树的 js 对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其它属性
React 版虚拟 DOM 的栗子:有一个div,其class属性值为 red,绑定了 click 事件,有两个 span 子节点
React
const vNode = {
key: null,
props: {
children: [ // 子元素们
{ type: 'span', ... },
{ type: 'span', ... }
],
className: "red" // 标签上的属性
onClick: () => {} // 事件
},
ref: null,
type: "div", // 标签名 or 组件名
...
}
如何创建 React 版虚拟 DOM 呢?
//React.createElement
createElement('div',{className:'red',onClick:()=> {}},[
createElement('span', {}, 'span1'),
createElement('span', {}, 'span2')
]
)
用 JSX 简化创建虚拟 DOM
// React JSX
<div className="red" onClick="{()=> {}}">
<span>span1</span>
<span>span2</span>
</div>
虚拟 DOM 的优点
减少 DOM 操作
-
虚拟 DOM 可以将多次操作合并为一次操作,比如添加 1000 个节点,原生 DOM 却是一个接一个操作的(减少频率)
-
虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如原生 DOM 要添加 1000 个节点,其实只有 10 个是新增的(减少范围)
跨平台
- 虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象
虚拟 DOM 的缺点
- 需要额外的创建函数,如 React 中的 createElement 或者 Vue 中的 h
可以通过 JSX 来简化成 XML 写法 ,但是需要额外的构建过程,严重依赖打包工具
DOM diff
- 虚拟 DOM 的对比算法
- 一个被称为 patch 的函数
- patches = patch(oldVNode, newVNode)
- patches 就是要运行的 DOM 操作
即给两个虚拟节点,返回对应的 DOM 操作
DOM diff 大概的逻辑
Tree diff
- 将新旧两棵树 逐层 对比,找出哪些节点需要更新
- 如果节点是组件就看 Component diff
- 如果节点是标签就看 Element diff
Component diff
- 如果节点是组件,就先看组件类型
- 类型不同直接替换(删除旧的)
- 类型相同则只更新属性
- 然后深入组件做 Tree diff(递归)
Element diff
- 如果节点是原生标签,则看标签名
- 标签名不同直接替换,相同则只更新属性
- 然后进入标签后代做 Tree diff(递归)
DOM diff 的优点
- 通过新旧虚拟DOM 这两个对象的差异(Diff算法),只把变化的部分重新渲染,提高渲染效率
DOM diff 的缺点
- 同级节点对比存在 bug (出现识别错误)
举个栗子
点击删除2,你以为2被删除了,结果却是:
实际计算机是把 2 变成 3,再把原来的 3 给删除了
如何解决?
- 用一个唯一的** id 作为 key** 进行标识
- 注意:不要用 index 作为 key,因为下标是可变且连续的,使用它依旧会出现以上 bug