目录:前端
1 前言node
2 技术发展史react
4 Virtual DOM 实现angularjs
5 Virtual DOM 树的差别(Diff算法)github
6 结语web
7 参考连接算法
我会尽可能把 Virtual DOM 应用场景、实现思路、算法讲述清楚,但愿你们阅读后,能让你后端
深刻理解 Virtual DOM。
写一个像下面的应用程序,这个表格能够根据不一样的字段进行升序或者降序。
最容易的方案是在你的 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 树变动。上面提供
的几种方法,其实都在解决同一个问题,那就是维护状态更新视图。若是咱们可以很好来
应对这个问题,就下降复杂性。
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。
用 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>
比较两个 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参考连接。
虽然只是很是粗糙的实践,但我相信 Virtual DOM 的原理是讲述通了。实际还须要处理事件监听、
状态监控。生成虚拟 DOM 时也能够加入 JSX 语法。固然这些事情都作了的话,就能够构造一个简
单的ReactJS了。
列表算法(Edit distance):https://en.wikipedia.org/wiki/Edit_distance
列表算法(Levenshtein distance):https://en.wikipedia.org/wiki/Levenshtein_distance
参考文章(图):http://www.javashuo.com/article/p-ghbzqlpi-ck.html
参考文章(性能原理):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