虚拟 DOM

1. 什么是虚拟 DOM

虚拟 DOM(Virtual DOM)这个概念相信大家都不陌生,从 React 到 Vue,虚拟 DOM 为这两个框架都带来了跨平台的能力(React-Native 和 Weex)。

实际上它只是一层对真实 DOM 的抽象,以 JavaScript 对象(VNode 节点)作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上。

在 JavaScript 对象中,虚拟 DOM 表现为一个 Object 对象。并且最少包含标签名(tag)、属性(attrs)和子元素对象(children)三个属性,不同框架对这三个属性的命名可能会有差别。

创建虚拟 DOM 是为了更好将虚拟的节点渲染到页面视图中,所以虚拟 DOM 对象的节点与真实 DOM 的属性一一照应。

通过 VNode,Vue 可以对这棵抽象树进行创建节点、删除节点以及修改节点的操作,经过 diff 算法得出一些需要修改的最小单位,再更新视图,减少了 dom 操作,提高了性能。

2. 为什么需要虚拟 DOM

DOM 引擎的解析和绘制很慢,DOM 元素非常庞大,页面的性能问题大部分都是由 DOM 操作引起的。真实的 DOM 节点,哪怕一个最简单的 div 也包含着很多属性。

举个例子:

  • 用传统的原生 API 或 jQuery 去操作 DOM 时,浏览器会从构建 DOM 树开始从头到尾执行一遍流程。
  • 当你在一次操作中需要更新 10 个 DOM 节点时,浏览器不会这么智能,收到第一个更新 DOM 请求后并不知道后续还有 9 次更新操作,因此会马上执行流程,最终执行 10 次流程。

而通过 VNode 同样更新 10 个 DOM 节点:虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地的一个 JS 对象中,最终将这个 JS 对象一次性 attach 到 DOM 树上,避免大量的无谓计算。

很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 带来的性能消耗。虽然这是虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 iOS 的原生组件,也可以是近期很火热的小程序,也可以是各种 GUI。

虚拟 DOM 在初次渲染 DOM 时会降低效率,但是在更新 DOM 时提高效率。

3. 如何实现虚拟 DOM

// 简易 VNode 类
class VNode {
  constructor(tag, attrs, children) {
    this.tag = tag
    this.attrs = attrs
    this.children = children
  }
}

// 创建虚拟 DOM 节点
const vnode = new VNode('div', { id: 'app' }, [
  new VNode('span', {}, ['hello']),
  new VNode('ul', {}, [
    new VNode('li', {}, ['1']),
    new VNode('li', {}, ['2'])
  ])
])

// 简易渲染函数:将 VNode 转为真实 DOM
function createElement(vnode) {
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode)
  }
  const el = document.createElement(vnode.tag)
  for (const key in vnode.attrs) {
    el.setAttribute(key, vnode.attrs[key])
  }
  vnode.children.forEach(child => {
    el.appendChild(createElement(child))
  })
  return el
}

来源整理自:vue3js.cn 面试官系列