虚拟 DOM 和 DOM diff

131 阅读2分钟

虚拟 DOM 是什么

一个能代表DOM树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他的属性

虚拟 DOM 的优点

  • 减少 DOM 操作
  1. 虚拟 DOM 可以将多次操作合并为一次操作,比如添加1000个节点,却是一个接一个操作的(减少频率)
  2. 虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如也是添加1000个节点,其实只有10个节点是新增的(减少范围)
  • 跨平台

虚拟 DOM 不仅可以变成 DOM, 还可以变成小程序、IOS应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象。

虚拟 DOM 的缺点

  1. 需要额外的创建函数(createElement或者h)
  2. 需要额外的转义构建工具(JSX简化成XML写法)
  3. 节点数量少时虚拟 dom 效率高,但是节点数量多时虚拟 dom 性能比不上原生dom(vue接近原生dom,react性能偏差)。

虚拟 DOM 的样子

  • React版
const vNode = {
  key: null,
  props: {
    children: [  // 子元素们
       { type: 'span', ... }, 
       { type: 'span', ... }
    ],
    className: "red" // 标签上的属性
    onClick: () => {} // 事件
  },
  ref: null,
  type: "div", // 标签名 or 组件名
  ...
}

  • Vue版
const vNode = {
  tag: "div", // 标签名 or 组件名
  data: {
    class: "red", // 标签上的属性
    on: {
      click: () => {} // 事件
    }
  },
  children: [ // 子元素们
    { tag: "span", ... },
    { tag: "span", ... }
  ],
  ...
}

创建虚拟 DOM 的方法

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

通过 babel 转为 createElement 形式

  • Vue Template
<div class="red" @click="fn">
  <span>span1</span>
  <span>span2</span>
</div>

通过 vue-loader 转为 h 形式

DOM diff

虚拟 DOM 的对比算法

DOM diff 是什么

  • 就是一个函数,我们称之为 patch
  • patches = patch(oldVNode, newVNode)
  • patches 就是要运行的 DOM 操作,可能长这样:
[
  {type: 'INSERT', vNode: ... },
  {type: 'TEXT',  vNode: ... },
  {type: 'PROPS', propsPatch: [...]}
]

DOM diff 大概逻辑

  1. Tree diff
  • 将新旧两棵树逐层对比,找出哪些节点需要更新
  • 如果节点是组件就看 Component diff
  • 如果节点是标签就看 Element diff
  1. Component diff
  • 如果节点是组件,就先看组件类型
  • 类型不同直接替换(删除旧的)
  • 类型相同则只更新属性
  • 然后深入组件做 Tree diff(递归)
  1. Element diff
  • 如果节点是原生标签,则看标签名
  • 标签名不同直接替换,相同则只更新属性
  • 然后进入标签后代做 Tree diff(递归)

DOM diff 的缺点