为何须要虚拟DOM?前端
若是对前端工做进行抽象的话,主要就是维护状态和更新视图,而更新视图和维护状态都须要DOM操做。其实近年来,前端的框架主要发展方向就是解放DOM操做的复杂性。vue
运行js的速度是很快的,大量的操做DOM就会很慢,时常在更新数据后会从新渲染页面,这样形成在没有改变数据的地方也从新渲染了DOM 节点,这样就形成了很大程度上的资源浪费。git
在jQuery出现之前,咱们直接操做DOM结构,这种方法复杂度高,兼容性也较差。有了jQuery强大的选择器以及高度封装的API,咱们能够更方便的操做DOM,jQuery帮咱们处理兼容性问题,同时也使DOM操做变得简单。程序员
可是聪明的程序员不可能知足于此,各类MVVM框架应运而生,有angularJS、avalon、vue.js等,MVVM使用数据双向绑定,使得咱们彻底不须要操做DOM了,更新了状态,视图会自动更新。更新了视图数据状态也会自动更新,能够说MVVM使得前端的开发效率大幅提高。可是其大量的事件绑定使得其在复杂场景下的执行性能堪忧,有没有一种兼顾开发效率和执行效率的方案呢?由此引入Virtual DOM(虚拟DOM)。github
利用在内存中生成与真实DOM与之对应的数据结构,这个在内存中生成的结构称之为虚拟DOM 。算法
当数据发生变化时,可以智能地计算出从新渲染组件的最小代价并应用到DOM操做上。缓存
Virtual DOM 算法数据结构
所谓的 Virtual DOM 算法。包括几个步骤:app
1.用 JavaScript 对象结构表示 DOM 树的结构;而后用这个树构建一个真正的 DOM 树,插到文档当中;框架
2.当状态变动的时候,从新构造一棵新的对象树。而后用新的树和旧的树进行比较,记录两棵树差别;
3.把2所记录的差别应用到步骤1所构建的真正的DOM树上,视图就更新了。
Virtual DOM 本质上就是在 JS 和 DOM 之间作了一个缓存。能够类比 CPU 和硬盘,既然硬盘这么慢,咱们就在它们之间加个缓存。
既然 DOM 这么慢,咱们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操做内存(Virtual DOM),最后的时候再把变动写入硬盘(DOM)。
比较两棵虚拟DOM树的差别
比较两棵DOM树的差别是 Virtual DOM 算法最核心的部分,这也是所谓的 Virtual DOM 的 diff 算法。
两个树的彻底的 diff 算法是一个时间复杂度为 O(n^3) 的问题。可是在前端当中,你不多会跨越层级地移动DOM元素。因此 Virtual DOM 只会对同一个层级的元素进行对比:
上面的div
只会和同一层级的div
对比,第二层级的只会跟第二层级对比。这样算法复杂度就能够达到 O(n)。
在实际的代码中,会对新旧两棵树进行一个深度优先的遍历,这样每一个节点都会有一个惟一的标记,以下图所示:
Virtual DOM 算法实现
Virtual DOM 算法得实现主要是用三个函数:element,diff,patch。而后就能够实际的进行使用,以下面代码所示:
// 1. 构建虚拟DOM var tree = el('div', {'id': 'container'}, [ el('h1', {style: 'color: blue'}, ['simple virtal dom']), el('p', ['Hello, virtual-dom']), el('ul', [el('li')]) ]) // 2. 经过虚拟DOM构建真正的DOM var root = tree.render() document.body.appendChild(root) // 3. 生成新的虚拟DOM var newTree = el('div', {'id': 'container'}, [ el('h1', {style: 'color: red'}, ['simple virtal dom']), el('p', ['Hello, virtual-dom']), el('ul', [el('li'), el('li')]) ]) // 4. 比较两棵虚拟DOM树的不一样 var patches = diff(tree, newTree) // 5. 在真正的DOM元素上应用变动 patch(root, patches)
diff算法
用 三大策略 将O(n^3)复杂度 转化为 O(n)复杂度
Web UI中DOM节点跨层级的移动操做特别少,能够忽略不计。
拥有相同类的两个组件 生成类似的树形结构,
拥有不一样类的两个组件 生成不一样的树形结构。
对于同一层级的一组子节点,经过惟一id区分。
tree diff
(1)经过updateDepth对Virtual DOM树进行层级控制。
(2)对树分层比较,两棵树只对同一层次节点进行比较。若是该节点不存在时,则该节点及其子节点会被彻底删除,不会再进一步比较。
(3)只需遍历一次,就能完成整棵DOM树的比较。
diff只简单考虑同层级的节点位置变换,若是是跨层级的话,只有建立节点和删除节点的操做。
如上图所示,以A为根节点的整棵树会被从新建立,而不是移动,所以官方建议不要进行DOM节点跨层级操做,能够经过CSS隐藏、显示节点,而不是真正地移除、添加DOM节点。