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