虚拟 DOM 与 DOM diff

395 阅读3分钟

有关于 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

参考文献:
Vue2.0 v-for 中 :key 到底有什么用
React 虚拟 Dom 和 diff 算法