源码级别人话说:Virtual DOM和DOM diff算法

来源:这里教程网 时间:2026-03-03 16:28:23 作者:

前言:在我还没有理解虚拟dom概念和dom diff算法之前,经常会不经意间从各种渠道听到这两个词语。当时每每听到看到,尤其是群友吹水谈到这两个词语的时候,我都会感觉我的技术水平菜人一等,这就更不用说我面试的时候有多害怕面试官会提到这些了。我相信在看官当中,也会有不少当时的我。这篇文章的目的就是希望能够通过分享我对这两个概念的理解,帮助阁下(当时的我)建立或者加深对虚拟dom和dom diff算法的理解。好的,下面进入正文,对于virtual dom和dom diff算法的系统化认识,我预先用脑图为阁下做了以下整理: 下面我会分成以下两个部分来展开探讨,以求能够帮助您建立自己对Virtual DOM和DOM diff算法的理解:

理解Virtual DOM Virtual DOM库,Snabbdom源码分析

一:理解Virtual DOM

1.Virtual DOM概念

说到Virtual Dom那肯定要从Dom说起,毕竟如果没有Dom,你Virtual个屁哦。所以在探讨Virtual Dom的概念之前,我们再回顾一波Dom。MDN中对DOM的定义是:DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML文档交互的API。DOM是载入到浏览器中的文档模型,以节点树的形式来表现文档,每个节点代表文档的构成部分(例如:页面元素、字符串或注释等等)。我不知道上面是不是意指DOM具有两个概念,对于第一个概念,我把它理解成和BOM、ORM是一类的概念。对于第二部分,我就直接把它当作解释由DOM节点组成的DOM树来理解了。对于Virtual DOM,我没有找到一种权威定义,网上的一种主流解释是:Virtual Dom(Document Object Model)是由普通的JS对象来描述DOM对象,因为不是真实的DOM对象,所以叫Virtual DOM。我并不认可上述概念,我觉得那只是对Virtual DOM的一个现象解释,但是而现象不等于概念。既然没有权威定义,那么我也不在这里咬文嚼字给它下定义了,我对他的理解是很简单,就是Virtual的DOM。DOM一词无须再解释,下面分享一下我对于virtual这个词的理解。对应于DOM第一部分概念:我认为virtual意指在实际的dom操作之前加一层 虚拟DOM操作切面,在这个切面中可以拦截、改变将要发生的DOM操作。对应于DOM第二部分概念:我认为virtual意指在DOM树之前,有一颗可以一一映射成它的 虚拟DOM树(由普通JS模拟的节点对象组成)

如果我的理解有问题或者阁下对Virtual DOM有什么高见,欢迎评论处指点一下,谢谢。

好的,有了以上认知之后,接下来我们演示一个demo,实际感受一下DOM渲染UI与Virtual DOM渲染UI两种方式的不同。

2.Virtual DOM渲染UI示例

假设我们的需求如下是实现一个如下html结构的web ui:

<div>     hello     <!-- this is a notes node -->     <ul>         <li id="1" class="li-1">first li</li>         <li id="2" class="li-2">second li</li>     </ul> </div> 复制代码

为了探讨DOM更新,我们让我们的示例中div的text(即hello部分)每一秒都会从[hello、visual、world]中随机选择一个显示,也就是说我们的探讨会涉及以下两个部分,即:

dom结构初始化 dom结构更新

好的,下面我们先用原生DOM来渲染上述UI结构,而后借由Virtual DOM库snabbdom来渲染上述UI结构。 原生DOM渲染UI示例:在直接丢出一坨代码恶心阁下之前,我有必要再为阁下明确一点,下述这个示例演示目的是:实践如何通过原生DOM方式,把我们用JS生成的DOM结构初始化或更新渲染到已有的id为app的dom位置上。我再来缕一下如下示例的编写思路,以便帮助阁下理解,思路如下:

1.封装一个createDivNode函数,用于以原生dom方式创建整个DIV节点 2.封装一个render函数,用于初始化渲染UI结构或者更新UI结构 3.调用render函数初始化div的DOM结构 4.调用render函数更新div的DOM结构

看示例的时候,建议阁下联系思考一下vue和react框架提供的render函数。

    <div id="app"></div> <!-- div的DOM结构渲染位置 -->     <script>       let app = document.getElementById("app");       // 原生dom api创建整个DIV节点       function createDivNode() {         const divNode = document.createElement("div");         const textNode = document.createTextNode(["hello", "visual", "dom"][Date.now()%3]);         divNode.appendChild(textNode);         const notesNode = document.createComment("this is a notes node");         divNode.appendChild(notesNode);         function createLiNode(props, text) {           const li = document.createElement("li");           for (key in props) {             const attr = document.createAttribute(key);             attr.value = props[key];             li.setAttributeNode(attr);           }           const textNode = document.createTextNode(text);           li.appendChild(textNode);           return li;         }         divNode.appendChild(createLiNode({ id: 1, class: "li-1" }, "first li"));         divNode.appendChild(           createLiNode({ id: 2, class: "li-2" }, "second li")         );         return divNode;       }              // 渲染dom       function render(action) {         function replaceDom() {           const newDiv = createDivNode();           app.parentNode.appendChild(newDiv);           app.parentNode.removeChild(app);           app = newDiv;         }         switch (action) {           case "init":             replaceDom();             break;           case "update":             // 更新时直接替换整个div的DOM结构             replaceDom();             break;         }       }       // 初始化DOM结构       render("init");       // 更新DOM结构       setInterval(() => {         render("update");       }, 1000);     </script> 复制代码

snabbdom渲染UI示例:同样,我有必要再为阁下明确一点,下述这个示例演示目的是:实践如何通过虚拟DOM方式,把我们用JS生成的DOM结构初始化或更新渲染到已有的id为app的dom位置上。接下来是如下示例的编写思路:

1.封装一个createDivNode函数,用于以virtual dom方式创建整个DIV节点 2.封装一个render函数,用于初始化渲染UI结构或者更新UI结构 3.调用render函数初始化div的DOM结构 4.调用render函数更新div的DOM结构

看示例的时候,建议阁下联系思考一下vue和react框架提供的render函数。

   <div id="app"></div> <!-- div的DOM结构渲染位置 -->    <script src="https://cdn.bootcss.com/snabbdom/0.7.4/snabbdom.js"></script>    <script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.4/h.js"></script>    <script>      let app = document.getElementById("app");      const patch = snabbdom.init([]);      const h = snabbdom.h;            // virtual dom创建整个DIV节点      function createDivVNode() {        function createLiVNode(sel, text) {          return h(sel, text);        }        // 调用snabbdom的h函数创建Viratul Node对象        const vnode = h("div", [          ["hello", "visual", "dom"][Date.now()%3],          h("!", "this is a notes node"),          createLiVNode("li#1.li-1", "first li"),          createLiVNode("li#2.li-2", "second li"),        ]);        return vnode;      }      // 渲染DOM结构      function render(action) {        switch (action) {          case "init":            // 调用sanbbdom的patch函数渲染div的虚拟DOM结构到app位置上            app = patch(app, createDivVNode());            break;          case "update":            // 调用sanbbdom的patch函数更新div的虚拟DOM结构到app位置上            app = patch(app, createDivVNode());            break;        }      }      // 初始化DOM结构      render("init");      // 更新DOM结构      setInterval(() => {        render("update");      }, 1000);    </script>

相关推荐