深度理解virtualdom

吴佰清 吴佰清     2022-08-28     379

关键词:

目录:

1 前言

2 技术发展史

3 Virtual DOM 算法

4 Virtual DOM 实现

5 Virtual DOM 树的差异(Diff算法)

6 结语

7 参考链接

 

1 前言

我会尽量把 Virtual DOM 应用场景、实现思路、算法讲述清楚,希望大家阅读后,能让你

深入理解 Virtual DOM。

 

2 技术发展史

写一个像下面的应用程序,这个表格可以根据不同的字段进行升序或者降序。

最容易的方案是在你的 JavaScript 代码里面存储这样的数据:

var sortKey = "name" // 排序的字段,名称(name)、年龄(age)
var sortType = "ASC" // 升序还是逆序
var data = [{...}, {...}, {..}, ..] // 表格数据

用三个变量分别存储当前排序的字段、排序方向、还有表格数据;然后给表格头部加

点击事件,当用户点击排序字段时,会根据上面几个变量存储的值来对内容进行排序,

然后用 JS 操作 DOM,更新页面的排序状态和表格内容。

 

这么做会导致一个问题:应用越来越复杂,需要在 JS 里面维护的字段也越来越多,

要监听的事件和在事件中回调更新页面的 DOM 也越来越多,最终应用变得难以维护。

 

之后研究出了 MVC、MVP 的架构模式,希望从代码组织的方式来降低维护复杂应用的

难度。但是 MVC 架构没办法减少你所维护的状态,也没有降低状态更新时需要对页面

的更新操作,你需要操作的 DOM 还是需要操作,只是换了个地方而已。

 

既然状态改变了要操作相应的 DOM 元素,为什么不做一个东西可以让视图和状态进行

绑定?让状态变更视图自动跟着变更,就不用手动更新页面了。这就是后来的 MVVM

式,只要在模版中声明视图组件是和什么状态进行绑定的,双向绑定引擎就会在状态

更新的时候自动更新视图,MVVM 可以能很好的降低维护状态以及减少视图的复杂程度。

 

但这不是唯一办法,还有一个非常直观的方法,可以大大降低视图更新的操作。一旦

态发生了变化,就用模版引擎重新渲染整个视图,然后用新的视图更换掉旧的视图。

像上面的表格,当用户点击的时,还是在 JS 里面更新状态,但是页面更新就不用手

作 DOM 了,直接把整个表格用模版引擎重新渲染一遍,然后设置一下 innerHTML 。

么这个方法会有个很大的问题,会导致 DOM 操作变慢,因为任何的状态变更都要

新构造整个 DOM,性价比很低。对于局部的小视图的更新,这样没有问题(backbone

就是这么干的)。但对于大型视图,需要更新页面较多局部视图时,这样的做法就非常

可取。

 

Virtual DOM 也是这么做的,只是加了一些步骤来避免了整棵 DOM 树变更。上面提供

的几种方法,其实都在解决同一个问题,那就是维护状态更新视图。如果我们能够很好

应对这个问题,就降低复杂性。

 

3 Virtual DOM 算法

DOM 很慢,为啥说它慢,先看一下 Webkit 引擎,所有浏览器都遵循类似的工作流,只

是在细节处理有些不同。一旦浏览器接收到一个 HTML 文件,渲染引擎 Render Engine

就开始解它,根据 HTML 元素 Elements 对应地生成 DOM 节点 Nodes,最终组成一

棵 DOM 树。

 

构造了渲染树以后,浏览器引擎开始着手布局 Layout。布局时,渲染树上的每个节点根

其在屏幕上应该出现的精确位置,分配一组屏幕坐标值。接着,浏览器将会通过遍历渲染树,

调用每个节点的 Paint 方法来绘制这些 Render 对象。Paint 方法根据浏览器平台,使用不

同的 UI后端 API(Agnostic UI Backend API)通过绘制,最终将在屏幕上展示内容。只要

在这过程中进行一次 DOM 更新,整个渲染流程都会重做一遍。

 

把一个简单的 div 元素的属性都打印出来,你会看这些。

align, onwaiting, onvolumechange, ontimeupdate, onsuspend, onsubmit,
onstalled, onshow, onselect, onseeking, onseeked, onscroll, onresize,
onreset, onratechange, onprogress, onplaying, onplay, onpause,
onmousewheel, onmouseup, onmouseover, onmouseout, onmousemove,
onmouseleave, onmouseenter, onmousedown, onloadstart,
onloadedmetadata, onloadeddata, onload, onkeyup, onkeypress,
onkeydown, oninvalid, oninput, onfocus, onerror, onended, onemptied,
ondurationchange, ondrop, ondragstart, ondragover, ondragleave,
ondragenter, ondragend, ondrag, ondblclick, oncuechange,
oncontextmenu, onclose, onclick, onchange, oncanplaythrough,
oncanplay, oncancel, onblur, onabort, spellcheck, isContentEditable,
contentEditable, outerText, innerText, accessKey, hidden,
webkitdropzone, draggable, tabIndex, dir, translate, lang, title,
childElementCount, lastElementChild, firstElementChild, children,
nextElementSibling, previousElementSibling, onwheel,
onwebkitfullscreenerror, onwebkitfullscreenchange, onselectstart,
onsearch, onpaste, oncut, oncopy, onbeforepaste, onbeforecut,
onbeforecopy, webkitShadowRoot, dataset, classList, className,
outerHTML, innerHTML, scrollHeight, scrollWidth, scrollTop,
scrollLeft, clientHeight, clientWidth, clientTop, clientLeft,
offsetParent, offsetHeight, offsetWidth, offsetTop, offsetLeft,
localName, prefix, namespaceURI, id, style, attributes, tagName,
parentElement, textContent, baseURI, ownerDocument, nextSibling,
previousSibling, lastChild, firstChild, childNodes, parentNode,
nodeType, nodeValue, nodeName

来看看空的 div 元素有多少属性要实现,这还只是第一层的自有属性,没包括原型链继承而来

的。如果触发了页面事件,就就会导致页面重排。相对于 DOM 对象,原生的 JavaScript 处理

起来才会更快且更简单。

 

DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来。

var olE = {
  tagName: 'ol', // 标签名
  props: { // 属性用对象存储键值对
    id: 'ol-list'
  },
  children: [ // 子节点
    {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
  ]
}

对应 HTML 写法是:

<ol id='ol-list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ol>

DOM 我们都可以用 JavaScript 对象来表示。那反过来,就可以用 JavaScript 对象表示的树结

构来构建一个真正的 DOM 。当状态变更时,重新渲染这个 JavaScript 的对象结构,实现视图

的变更,结构根据变更的地方重新渲染。

 

这就是所谓的 Virtual DOM 算法:

用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文

档当中当状态变更时,重新构造一棵新的对象树。然后用新的树和旧的树进行比较两个数的差异。

然后把差异更新到久的树上,整个视图就更新了Virtual DOM 本质就是在 JS 和 DOM 之间做

了一个缓存。既然已经知道 DOM 慢,就在 JS 和 DOM 之间加个缓存。JS 先操作 Virtual DOM

对比排序/变更,最后再把整个变更写入真实 DOM。

 

4 Virtual DOM实现

用 JavaScript 表示一个 DOM 节点非(wo)常(cui)的(niu)简(bi)单(ne),只需要记

录它的节点类型、属性,还有子节点。

export default Ele = (tagName, props, children) => {
  this.tagName = tagName
  this.props = props
  this.children = children
}
<ol id='ol-list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ol>

例如上面的 DOM 结构就可以简单的表示:

import * as el from 'Ele';
var ol = el('ol', {id: 'ol-list'}, [
  el('li', {class: 'item'}, ['Item 1']),
  el('li', {class: 'item'}, ['Item 2']),
  el('li', {class: 'item'}, ['Item 3'])
]);

现在 ol 只是一个 JavaScript 对象表示的 DOM 结构,但页面上并没有这个结构。我们可以根据这

个 ol 构建来生成真正的 ol。新增一个 render 方法,根据 tagName 构建一个真正的 DOM,然

后生成 DOM 属性、连接子结构等等。

Ele.prototype.render = function () {
  var e = document.createElement(this.tagName); // 创建元素
  var props = this.props;
 
  for (var propName in props) { // 设置 DOM 属性
    var propValue = props[propName];
    e.setAttribute(propName, propValue);
  }
 
  var children = this.children || [];
 
  children.forEach(function (child) {
    var childE = (child instanceof Element)
      ? child.render() // 子节点也是虚拟 DOM,递归构建
      : document.createTextNode(child); // 字符串,构建文本节点
    e.appendChild(childE);
  });
 
  return e;
}

最后只需要 render。

var olE = Ele.render()
document.body.appendChild(olE);

上面的 olE 是真正的 DOM 节点,把它 append 到 body 中,这样就有了真正的 ol DOM 元素。

<ol id='ol-list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ol>

 

5 Virtual DOM 树的差异介绍(Diff算法)

比较两个 DOM 树的差异是 Virtual DOM 算法最核心的部分,这也是所谓的 Virtual DOM 的

diff 算法。在前端当中,很少会跨越层级地移动 DOM 元素。所以 Virtual DOM 只会对同一个

层级的元素进行对比,下面的 div 只会和同一层级的 div 对比,第二层级的只会跟第二层级对

比。采用的是深度优先遍历,来记录差异,这样每个节点都会有一个唯一的标记。

 

差异是指的是什么呢?DOM 替换掉原来的节点,如把上面的 div 换成了 section 进行移动、删

除、新增子节点,例如上面 div 的子节点,把 p 和 span 顺序互换修改了节点的属性。对于文本

节点,文本内容可能会改变。

 

如果我把左侧的 p、span、div 反过来变成 div、p、span 怎么办?按照差异正常会被替换掉,

但这样 DOM开销就会异常的大了。而 React 帮我们做到不需要替换节点,而只需要经过节点移

动就可以达到。至于怎么变动,会牵扯到太多的对比算法不一一介绍,有兴趣的了解下列表对比

算法,详细见5参考链接 

 

6 结语

虽然只是非常粗糙的实践,但我相信 Virtual DOM 的原理是讲述通了。实际还需要处理事件监听、

状态监控。生成虚拟 DOM 时也可以加入 JSX 语法。当然这些事情都做了的话,就可以构造一个简

单的ReactJS了。

 

7 参考链接

列表算法(Edit distance):https://en.wikipedia.org/wiki/Edit_distance

列表算法(Levenshtein distance):https://en.wikipedia.org/wiki/Levenshtein_distance

参考文章(图):https://segmentfault.com/a/1190000000753400

参考文章(性能原理):http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/

参考文章(浏览器工作流):http://www.jianshu.com/p/f75c1f0af3f0

参考文章(Webkit相关):https://webkit.org/blog/

参考代码(Vtree):https://github.com/Matt-Esch/virtual-dom/blob/master/vtree/diff.js

深度剖析:如何实现一个virtualdom算法

 过程其实并不复杂,只是把现有的dom更需要修改的dom做一个比较,原来的是整个页面重新渲染,而如今是局部的dom更新,甚至只更新文本内容当然不是简单的比较,比如一下排序,会调换dom的顺序,而不是删除后重建 https://github.co... 查看详情

理解virtualdom(摘)及评价

...框架是绝对比不过优化后的原生代码的。 二、什么是VirtualDOMVirtualDOM的概念有很多解释,从我的理解来看,主要是三个方面,分别是:一个对象,两个前提,三个步骤。一个对象指的是VirtualDOM是一个基本的JavaScript对象,也... 查看详情

如何理解vue,virtualdom(代码片段)

Vue.js2.0窥探之VirtualDOM到底是什么?Vue.js2.0已经发布,并且在其中新添加如了一些新功能。其中一个功能就是“VirtualDOM”。 VirtualDOM是什么?在之前,React和Ember早就开始用虚拟DOM技术来提高页面更新的速度了。若想了解它是... 查看详情

实现一个virtualdom算法

1前言本文会在教你怎么用300~400行代码实现一个基本的VirtualDOM算法,并且尝试尽量把VirtualDOM的算法思路阐述清楚。希望在阅读本文后,能让你深入理解VirtualDOM算法,给你现有前端的编程提供一些新的思考。本文所实现的完整代... 查看详情

虚拟domvituraldomtree

  提起VirtualDOM,总是给人一种高深莫测的感觉,大家都知道它比DOM快。那么VirtualDOM到底是何方神圣呢?在深入理解VirtualDOM之前,先让我们回顾一下DOM。一、什么DOM?所谓DOM,就是HTML、XML、XHTML的一种抽象描述,它会把这... 查看详情

virtualdom简单了解

...,参与开源项目,都可以让codeing获得快乐。文章概要:virtualdom基本概念,存在原因。virtualdom简单应用。vue中的 virtualdom应用。virtualdom大概实现以及算法。小结 查看详情

vue的虚拟dom(virtualdom)(代码片段)

...得更好的控制。  渲染函数:渲染函数是用来生成VirtualDOM的;VNode虚拟节点:vnode可以理解成dom节点的描述对象,它描述了应该怎样去创建真实的DOM节点;patch(patching算法):虚拟DOM最核心的部分,它可以将vnode渲染成真实... 查看详情

virtualdom

VirtualDOM是React的核心技术之一。 virtual  英[?v?:t?u?l]   美[?v?:rt?u?l]adj.   实质上的,事实上的;(计算机)虚拟的;<物>有效的,虚像的;(粒子)实际存在的;实际上,VirtualDOM包含:JavascriptDOM模型... 查看详情

深度学习中的iou概念理解

添加链接描述 查看详情

深度学习中的iou概念理解

添加链接描述 查看详情

深度学习中的iou概念理解

添加链接描述 查看详情

三维点云深度学习与语义理解方法及关键技术

作者:若晨Date:2020-05-14来源:三维点云深度学习与语义理解方法及关键技术这一次给大家带来在自动驾驶中关于三维点云的深度学习方法应用、三维场景语义理解的方法以及对应的关键技术介绍。1.三维点云深度学习在深度学... 查看详情

深度学习入门基础二简单理解transformer

【深度学习入门基础】二、简单理解Transformer文章目录【深度学习入门基础】二、简单理解Transformer自注意力层多头注意力Transformer输入(输出)嵌入位置编码Add和NormFeedForwardLinearNxseq2seq模型和marked注意力机制训练其他这... 查看详情

深度学习入门基础二简单理解transformer

【深度学习入门基础】二、简单理解Transformer文章目录【深度学习入门基础】二、简单理解Transformer自注意力层多头注意力Transformer输入(输出)嵌入位置编码Add和NormFeedForwardLinearNxseq2seq模型和marked注意力机制训练其他这... 查看详情

virtualdom算法

...react.js javascript目录:1前言2对前端应用状态管理思考3VirtualDOM算法4算法实现4.1步骤一:用JS对象模拟DOM树4.2步骤二:比较两棵虚拟DOM树的差异4.3步骤三:把差异应用到真正的DOM树上5结语6References1前言本文会在教你怎么用300~400... 查看详情

virtualdom的实践

 最近基于virtualdom写了一个小框架-aoy。  aoy是一个轻量级的mvvm框架,基于VirtualDOM。虽然现在看起来很单薄,但我做了完善的单元测试,可以放心使用。aoy的原理可以说和vue的实现大同小异,对于想了解原理的同学,... 查看详情

facerecognition[翻译][深度学习理解人脸]

...引位置保持不变,方便直接去原文中找参考文献。近些年深度卷积神经网络的发展将各种目标检测和识别问题大大的向前推进了不少。这同时也得益于大量的标注数据集和GPU的使用,这些方面的发展使得在无限制的图片和视频中... 查看详情

深度学习理解内容初

深度学习采用神经网络解决线性不可分的问题。既然是深度学习,就是包含多个隐层。觉得知乎大神说了一段很有意思的话:1.初恋期。相当于深度学习的输入层。别人吸引你,肯定是有很多因素,比如:身高,身材,脸蛋,学... 查看详情