浅析虚拟 DOM 与 DOM diff

159 阅读2分钟

一、虚拟DOM

1. 虚拟DOM是个啥

      简单来说虚拟DOM就是一个能代表 DOM 树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性。它长这样:

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

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

2. 虚拟 DOM 的优点

  • 减少 DOM 操作

       减少DOM操作的次数,虚拟 DOM 可以将多次操作合并为一次操作,比如你添加 1000 个节点,却是一个接一个操作的。

       减小DOM操作的范围,虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如你添加 1000 个节点,其实只有 10 个是新增的。 

  • 跨平台

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

3. 虚拟 DOM 的缺点

需要额外的创建函数。如 createElementh

//React.createElement
createElement('div',{className:'red',onClick:()=> {}},[
    createElement('span', {}, 'span1'),
    createElement('span', {}, 'span2')
  ]
)

//Vue(只能在 render 函数里得到 h)
h('div', {
  class: 'red',
  on: {
    click: () => { }
  },
}, [h('span',{},'span1'), h('span', {}, 'span2']) 

但可以通过 JSX 来简化成 XML 写法:

//React JSX
<div className="red" onClick="{()=> {}}">
    <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

1. DOM diff是个啥

DOM diff就是一个函数,我们称之为 patch,它会对比 oldNodenewNode 的区别,从而减少不必要的渲染:patches = patch(oldVNode, newVNode),patches 就是要运行的 DOM 操作,可能长这样:

[
  {type: 'INSERT', vNode: ... },
  {type: 'TEXT',  vNode: ... },
  {type: 'PROPS', propsPatch: [...]}
] 

DOM diff 可能的大概逻辑是:

  • Tree diff

将新旧两棵树逐层对比,找出哪些节点需要更新,

如果节点是组件就看 Component diff

如果节点是标签就看 Element diff

  • Component diff

如果节点是组件,就先看组件类型,

类型不同直接替换(删除旧的,

类型相同则只更新属性,然后深入组件做 Tree diff(递归)。

  • Element diff

如果节点是原生标签,则看标签名,

标签名不同直接替换,相同则只更新属性,然后进入标签后代做 Tree diff(递归)。

2. DOM diff 优缺点

DOM diff通过对比新旧虚拟DOM 这两个对象的差异,最终只把变化的部分重新渲染,提高渲染效率的过程。但DOM diff同级节点对比存在 bug,需要使用key来辅助diff的对比,从而消除bug。