[H5]DOM和Virtual DOM之间的区别

DOM

  • DOM代表了Document Object Model
  • DOM是一种抽象化的结构性文本
  • DOM的作用只是需要能够直接获取东西
    对于Web Developers来说,这种结构性的文本就是HTML code,并且 DOM被简单的称为HTML DOM。HTML code中的elements就变成了DOM中的nodes

所以,既然HTML是一种文本,那么DOM就是HTML在内存中的表现形式。

将它与程序的一个实例进行比较。你可以在同一个程序的多个过程,就像你可以有多个相同的HTML DOM(例如同一页面加载多标签)。

HTML DOM提供了能够过去和修改Node的API,例如:getElementByIdremoveChild
我们通常使用JavaScript来操作DOM对象。

下面举个栗子:

var item = document.getElementById("hei");
item.parentNode.removeChild(item);

document对象是HTML DOM中root node的一个抽象体。

遇到的问题

由于HTML文档的结构允许,所以HTML DOM使用的是树结构。这就很厉害了,因为通过树结构我们可以很容易地遍历树。不幸的是,很容易操作并不意味着很快
现在的WEB程序中,DOM树�非常巨大。因为我们越来越推向动态Web应用程序(Single Page Applications - SPAs),我们需要大量的不断的修改DOM树。�这真的非常痛苦。
顺便说一下,假如我自己设法创造一个5GB +源网页。它其实不是很难。但是考虑一下,我们将要在DOM使成千上万的div。记住,我们是现代的程序猿,我们写出的代码应该很容易管理!我们的方法,处理事件-点击提交,很多,型INS…
比方说,典型的jQuery事件处理程序看起来像这一样:
1、查找对事件感兴趣的每一个节点
2、必要时更新
其中有两个问题:

  • 很难管理。
    想象一下,你必须调整一个事件处理程序。如果你失去了背景,你必须潜水真正深入到代码,甚至知道发生了什么事。

  • 耗时和错误风险。
    它的效率很低。我们真的需要手动做所有这些调查结果吗?也许我们可以更聪明,并提前告诉哪些节点是要更新?

那么Virtual DOM则是为了解决以上这些问题。

Virtual DOM

首先需要讲明的一点Virtual DOM技术并不是React发明的,但是React确实是使他发扬光大的。

Virtual DOM是HTML DOM的抽象。它是轻量级和脱离浏览器的具体实施细节。因为DOM本身已经是一个抽象的、虚拟的DOM,事实上,Virtual DOM是一个抽象的抽象。

实际上,Virtual DOM包含:

  • Javascript DOM模型树(VTree),类似文档节点树(DOM)
  • DOM模型树转节点树方法(VTree -> DOM)
  • 两个DOM模型树的差异算法(diff(VTree, VTree) -> PatchObject)
  • 根据差异操作节点方法(patch(DOMNode, PatchObject) -> DOMNode)

VTree

VTree模型非常简单,基本结构如下:

{ 
  // tag的名字 
  tagName: 'p', 
  // 节点包含属性 
  properties: { style: { color: '#fff' } }, 
  // 子节点 
  children: [], 
  // 该节点的唯一表示,后面会讲有啥用
  key: 1
}

所以我们很容易写一个方法来创建这种树状结构,例如React是这么创建的:

// 创建一个divreact.createElement('div', null, [ 
  // 子节点img 
  react.createElement('img', { src: "avatar.png", class: "profile" }), 
  // 子节点h3 
  react.createElement('h3', null, [[user.firstName, user.lastName].join(' ')])
]);

VTree -> DOM

这方法也不太难,我们实现一个简单的:

function create(vds, parent) { 
  // 首先看看是不是数组,如果不是数组统一成数组 
  !Array.isArray(vds) && (vds = [vds]); 
  // 如果没有父元素则创建个fragment来当父元素 
  parent = parent || document.createDocumentFragment(); var node; 
  // 遍历所有VNode 
  vds.forEach(function (vd) { 
      // 如果VNode是文字节点 
      if (isText(vd)) { 
        // 创建文字节点 
        node = document.createTextNode(vd.text); 
        // 否则是元素
      } else { 
        // 创建元素 
        node = document.createElement(vd.tag); 
      } 
      // 将元素塞入父容器 
      parent.appendChild(node); 
      // 看看有没有子VNode,有孩子则处理孩子
      VNode vd.children && vd.children.length && create(vd.children, node); 
      // 看看有没有属性,有则处理属性 
      vd.properties && setProps({ style: {} }, vd.properties, node); }); 
      return parent;}

diff(VTree, VTree) -> PatchObject

差异算法是Virtual DOM的核心,实际上该差异算法是个取巧算法(当然你不能指望用O(n^3)的复杂度来解决两个树的差异问题吧),不过能解决Web的大部分问题。
那么React是如何取巧的呢?

  • 分层对比

如图,React仅仅对同一层的节点尝试匹配,因为实际上,Web中不太可能把一个Component在不同层中移动。

  • 基于key来匹配
    还记得之前在VTree中的属性有一个叫key的东东么?这个是一个VNode的唯一识别,用于对两个不同的VTree中的VNode做匹配的。



    这也很好理解,因为我们经常会在Web遇到拥有唯一识别的Component(例如课程卡片、用户卡片等等)的不同排列问题。

  • 基于自定义元素做优化
    React提供自定义元素,所以匹配更加简单。


patch(DOMNode, PatchObject) -> DOMNode

由于diff操作已经找出两个VTree不同的地方,只要根据计算出来的结果,我们就可以对DOM的进行差异渲染。

总结:

  • 给定一个可以代表DOM结构的VTree结构
  • 通过vdom/create-element函数来创建DOM中的节点
  • 通过将vdom/patch函数将vtree/diff函数根据两个不同的vtree结构产生的patch包,更新的DOM中

引用

前沿技术揭秘
The difference between Virtual DOM and DOM

扩展阅读

Matt-Esch/virtual-dom

推荐阅读更多精彩内容