不只懂 Vue 语法:什麽是 Virtual DOM?Vue 如何利用 Virtual DOM?

问题回答

当我们更新资料和渲染画面时会频繁地新增和删除 DOM 元素,造成效能问题。因此,不论是 Vue 或 React 都有使用 Virtual DOM 来避免直接操控 DOM。当 Vue 侦测到资料有更新,就会再次渲染更新的部分。但为了效能的考虑,它不会把整个 DOM 换掉,而是先建立一个 Virtual DOM,与原本的 DOM 作出比较,并透过算法找出有差异的部分,再针对它们来更新旧有的 DOM。

以下会再作详细解说。

什麽是 DOM、Virtual DOM?

DOM 是以树状模型来看 HTML 文件

在进入主题前,先稍为重温什麽是 DOM。DOM 是以树状结构来显示一个 HTML 文件的模型,这个树模型由一个个 DOM 节点(元素)组成。Web APIs 有提供 document.createElementdocument.body.appendChild() 等语法来操控 DOM。

图片来源:https://www.runoob.com/js/js-htmldom.html

以上图为例,左边有一个 h1 标签,所以在该 HTML 文件中,应该会找到这个 DOM 节点:

<h1> 标题 </h1>

Virtual DOM 的意思

Virtual DOM 是透过 JavaScript 物件模拟 一个 DOM 节点(Node)。之後再经由负责渲染的函式,变成真正的 DOM 节点,最後被挂载到网页里,完成更新 DOM。

Vue 如何使用 Virtual DOM

Vue 的 Virtual DOM 是参考 Snabbdom 这个 Virtual DOM 套件来实现。在 Vue 我们把 Virtual DOM 称为 VNode。

简单总括流程如下:

  1. 用 JavaScript 物件记录现有的 DOM 结构
  2. 当资料状态改变时,建立 Virtual DOM 以及一个新的 DOM 结构
  3. 利用算法分析这两个 DOM 的差异,记录实际上要更新的 DOM
  4. 针对要更新的 DOM,执行渲染的函式,把 Virtual DOM 渲染到画面上,完成更新 DOM

关於第二点,之前的文章有提到 Vue 使用了 MVVM 模式:

View(画面) <---> ViewModel <---> Model (资料)

ViewModel 会监听 View 各种 DOM 的事件,并与相关的 Model 作绑定。当画面触发事件,就会触发修改资料,之後再跑渲染资料到画面的流程。

Vue 所使用 diff 算法的基本概念

Vue 使用 diff 算法来避免在更新 DOM 时把整个 DOM 全都更新,而是只针对有更动的 DOM 来作更新,提升效能。

在此简单总括 diff 算法。当资料有更动时,Vue 就会产生两个 DOM。一是 Virtual DOM,二是原本旧有的 DOM。此算法就会以同层级比较的方式,比较两者的 DOM 节点的差异,并针对作出更新。有关详细针对 Vue 原始码和概念解说,可参考这篇文章

实作一个 Virtual DOM

算法的部分就不在此深究,但可以试试用 JavaScript 练习实作一个 Virtual DOM 来理解概念。例如我想建立一个 DOM:

<div id="app">
    <a href="https://google.com">
        这是一段文字
    </a>
</div>

用 JavaScript 物件来表示:

const exampleNode = {
  tagName: "div",
  attrs: {
    id: "app",
  },
  children: [
    {
      tagName: "a",
      attrs: {
        href: "https://google.com",
      },
      children: ["这是一段文字"],
    },
  ],
};

按这样的结构,我们先建立一个负责产生 Virtual DOM 的 Class

class Element {
  constructor(tagName, { attrs = {}, children = [] }) {
    this.tagName = tagName;
    this.attrs = attrs;
    this.children = children;
  }
}

我们会用 Element 这个 Class 来建构出 Virtual DOM 的物件。接下来把之前提到的 DOM 结构,透过使用 Element Class 来建构:

const VNode = new Element("div", {
  attrs: {
    id: "app",
  },
  children: [
    new Element("a", {
      attrs: {
        href: "https://google.com",
      },
      children: ["这是一段文字"],
    }),
  ],
});

console.log(VNode) 查看目前 VNode 的值:

目前完成以 JavaScript 物件方式来显示我们想要达成的 DOM 结构。接下来就是把它转换为真正的 DOM。在 Element 加入 renderElement 函式,并使用 document.createElementElement.setAttribute()Element.appendChild 等 Web APIs 来建立真正的 DOM:

class Element {
  constructor(tagName, { attrs = {}, children = [] }) {
    this.tagName = tagName;
    this.attrs = attrs;
    this.children = children;
  }

  renderElement() {
    const element = document.createElement(this.tagName);

    for (const [attrName, attrValue] of Object.entries(this.attrs)) {
      element.setAttribute(attrName, attrValue);
    }

    this.children.forEach((child) => {
      const childElement =
        // 如果此 child 不是以 Element 建构出来,就代表它是 textNode
        child instanceof Element
          ? child.renderElement()
          : document.createTextNode(child); 

      // 把子 Node 塞进 父 Node 里
      element.appendChild(childElement);
    });

    return element;
  }
}

呼叫 renderElement 的方法,就能产出一个真正的 DOM:

const result = VNode.renderElement()

console.log(result) 看看结果:

最後把它挂载到网页上:

document.getElementById("app").appendChild(result);

完整程序码

https://codesandbox.io/s/yong-javascript-wu-jian-shi-zuo-virtual-dom-0wcjj?file=/src/index.js

总结

  • Vue、React 都有使用 Virtual DOM 来提升更新 DOM 的效能
  • Virtual DOM 是指利用 JavaScript 物件来模拟一个 DOM 节点的结构
  • 当资料更新时,会比较现有已更新的 Virtual DOM 和旧有的 DOM,并针对差异之处来更新 DOM

参考资料

Vue.js 技术揭秘 - Virtual DOM
深度剖析:如何实现一个 Virtual DOM 算法
从头打造一个简单的 Virtual DOM


<<:  Flutter基础介绍与实作-Day2 Flutter的安装流程和环境配置

>>:  Day 01:前言

机器学习:演算法

线性代数 LR:逻辑回归(Logistic Regression): 预测事件发生的机率(y=1)...

[第二十九天]从0开始的UnityAR手机游戏开发-攻击按钮和UI血条

在ChangeAnimation脚本中新增此程序码 public void AniSJskill1(...

[Day15] Flutter - 大海捞针不是办法 ( Dartz )

前言 Hi, 我是鱼板伯爵在原本的try&catch中我们可以截取大部分的错误,但是这仅能告诉我程序...

追求JS小姊姊系列 Day4 -- 我知道很怪,但你不好奇字串姐变身会怎样吗(下)

前情提要 倒在路边的我,醒来发现人早已不见,只好回家过节。 (时间来到了,回到家中的午餐後) **我...

[Day13] - 於 Django 中进行资料库设定

建立完环境之後,我们需要在Django的设定中,也告诉Django 资料库的资讯,Django 才会...