📜  Virtual DOM 如何理解?(1)

📅  最后修改于: 2023-12-03 14:48:19.944000             🧑  作者: Mango

Virtual DOM 如何理解?

Virtual DOM 是一种前端性能优化技术,它将页面上所有的 DOM 元素和属性抽象成一个 JavaScript 对象树,这个对象树就是 Virtual DOM,通过比较新旧两个 Virtual DOM,只更新发生变化的部分,从而避免了全量更新页面所带来的性能损耗。

为什么需要 Virtual DOM?

前端页面发生变化,最常见的方法就是直接操作 DOM,比如增删改查元素,修改元素属性等等。但是,真正的 DOM 操作对性能的消耗是非常大的,尤其是当页面元素较多时,频繁操作 DOM 可能导致页面卡顿和浏览器崩溃。

而 Virtual DOM 的出现,就是为了解决这个问题。在 Virtual DOM 的帮助下,开发者可以按需更新部分元素,而不是直接修改原始的 DOM,从而减少操作 DOM 的次数,提高了页面的渲染效率。

Virtual DOM 的工作原理

Virtual DOM 由三部分组成:

  1. 虚拟节点
  2. 渲染函数
  3. Diff算法
虚拟节点

虚拟节点是 Virtual DOM 的最基本单位,它对应一个真实的 DOM 节点,在 JavaScript 中用一个对象来描述。虚拟节点除了包含节点的标签名和属性外,还包括节点的子节点,这些子节点也可以是虚拟节点,从而形成一个 Virtual DOM 树。

比如下面代码所示的 Virtual DOM 树:

{
  tagName: 'div',
  props: { className: 'container' },
  children: [
    { tagName: 'h1', children: ['Hello Virtual DOM'] }
    { tagName: 'p', children: ['It is a beautiful day.'] }
  ]
}

上面的代码表示一个具有一个 div 标签和两个子元素的 Virtual DOM 树。

渲染函数

渲染函数将 Virtual DOM 对象转换成真正的 DOM 元素并插入到页面中。一般来说,渲染函数会接收一个 Virtual DOM 树作为参数,返回一个真实的 DOM 元素,这个过程叫做 Virtual DOM 渲染。

比如下面是一个简单的 Virtual DOM 渲染函数:

function render(vnode) {
  const el = document.createElement(vnode.tagName);
  for (const [key, value] of Object.entries(vnode.props)) {
    el.setAttribute(key, value);
  }
  for (const child of vnode.children) {
    if (typeof child === 'string') {
      el.appendChild(document.createTextNode(child));
    } else {
      el.appendChild(render(child));
    }
  }
  return el;
}

上面的渲染函数可以将前面代码中的 virtual DOM 树转换成如下的真实 DOM 树:

<div class="container">
  <h1>Hello Virtual DOM</h1>
  <p>It is a beautiful day.</p>
</div>
Diff 算法

虽然 Virtual DOM 可以减少操作 DOM 的次数,提高性能,但是每个更新周期仍然需要比较新旧两个 Virtual DOM,找出需要更新的部分,这个过程就是 Virtual DOM Reconciliation,也就是常说的 Diff 算法。

Diff 算法使用深度优先遍历算法,把两棵树按照从上到下、从左到右的顺序进行比较,找出需要更新的部分。

比如下面是一个简单的 Diff 算法实现:

function diff(oldNode, newNode) {
  if (oldNode === newNode) {
    return;
  }
  if (isText(oldNode) && isText(newNode) && oldNode !== newNode) {
    oldNode.parentNode.replaceChild(newNode, oldNode);
    return;
  }
  if (oldNode.tagName !== newNode.tagName) {
    oldNode.parentNode.replaceChild(render(newNode), oldNode);
    return;
  }
  const allAttrs = Object.assign({}, oldNode.props, newNode.props);
  for (const attr in allAttrs) {
    const oldValue = oldNode.props[attr];
    const newValue = newNode.props[attr];
    if (oldValue === newValue) {
      continue;
    }
    if (oldValue === undefined) {
      oldNode.setAttribute(attr, newValue);
    } else if (newValue === undefined) {
      oldNode.removeAttribute(attr);
    } else {
      oldNode.setAttribute(attr, newValue);
    }
  }
  // 比较子节点
  const oldChildren = Array.from(oldNode.children);
  const newChildren = Array.from(newNode.children);
  for (let i = 0; i < newChildren.length || i < oldChildren.length; i++) {
    diff(oldChildren[i], newChildren[i]);
  }
}

上面的 Diff 算法比较简单,但它可以完成大多数实际应用中的操作,从而帮助开发者更快速地更新页面。

总结

Virtual DOM 是一种前端性能优化技术,通过抽象出页面元素,将其转换成 JavaScript 对象树,再通过比较新旧两个 Virtual DOM 的差异,实现增量更新页面,减少操作 DOM 的次数,提高渲染效率。Virtual DOM 的三个核心部分是虚拟节点、渲染函数和 Diff 算法,其中渲染函数负责将 Virtual DOM 转换成真实的 DOM 元素,而 Diff 算法负责找出新旧两个 Virtual DOM 的差异,从而减少操作 DOM 的次数。Virtual DOM 得到了很多流行的前端框架(如 React、Vue 和 Angular)的广泛应用,并成为了前端性能优化的重要手段。