浅谈Vue中的虚拟DOM

前言

  Vue2.0引入了虚拟DOM,比Vue1.0的初始渲染速度提高了2~4倍,并大大下降了内存消耗。目前主流的前端框架Vue、React核心技术也都使用了虚拟DOM,你是否好奇为何要提出虚拟DOM,虚拟DOM是什么,它有什么优点?这一切的问题,都将在本篇揭晓。前端

为何要提出虚拟DOM

  在Web早期,页面的交互比较简单,没有复杂的状态须要管理,也不太须要频繁的操做DOM,随着时代的发展,页面上的功能愈来愈多,咱们须要实现的需求也愈来愈复杂,DOM的操做也愈来愈频繁。经过js操做DOM的代价很高,由于会引发页面的重排重绘,增长浏览器的性能开销,下降页面渲染速度,既然操做DOM的代价很高那么有没有那种方式能够减小对DOM的操做?这就是为何提出虚拟DOM一个很重要的缘由。node

Virtual DOM 是什么?

  Vue.js经过编译将模版转换成渲染函数(render),执行渲染函数就能够获得一个以vnode节点(JavaScript对象)做为基础的树形结构,vnode节点里面包含标签名(tag)、属性(attrs)和子元素对象(children)等等属性,这个树形结构就是Virtual DOM,简单来讲,能够把Virtual DOM理解为一个树形结构的JS对象。git

模板转换成视图的过程整个过程: github

image.png
咱们先对上图几个概念嵌入解释:

  • 渲染函数:渲染函数是用来生成虚拟DOM的。Vue推荐使用模版来构建咱们的应用界面,在实现中Vue布局模版编译成渲染函数,固然咱们也能够不写模版,直接写渲染函数,这样子更接近编译后的模版。
  • vnode虚拟节点:它能够表明一个真实的DOM节点经过createElement方法能将vnode渲染成DOM节点,简单地说,虚拟节点能够理解成节点描述对象,它描述了应该怎样去建立真实的DOM节点。
  • patch(也称为patching算法):虚拟DOM最核心的部分,它能够将vnode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不一样,而后根据对比结果找出须要更新的的节点进行更新。

对于虚拟DOM,我们来看一个简单的实例,就是下图所示的这个,详细的阐述了模板 → 渲染函数 → 虚拟DOM树 → 真实DOM的一个过程 算法

image.png

为何虚拟DOM能够提升渲染速度

  传统方式用js操做DOM会有不少额外的DOM操做,例如,一个ul标签下有不少个li标签,其中只有一个li有变化,这种状况下若是使用新的ul去替代旧的ul,其实除了那个发生变化的li节点以外,其余节点都不须要从新渲染。因为DOM操做比较慢,因此这些DOM操做在性能上会有必定的浪费,避免这些没必要要的DOM操做会提高很大一部分性能(减小重排重绘从而节省浏览器的性能开销)。浏览器

  为了不没必要要的DOM操做,虚拟DOM在虚拟节点映射到视图的过程当中,将虚拟节点与上一次渲染视图缓存的虚拟节点(oldVnode)作对比,找出真正须要更新的节点来进行DOM操做,从而避免操做其余无任何改动的DOM。 其实虚拟DOM在Vue.js中主要作了两件事:缓存

  • 提供与真实DOM节点所对应的虚拟节点vnode
  • 将虚拟节点vnode和旧虚拟节点oldVnode进行比对,而后更新视图 对两个虚拟节点进行对比是虚拟DOM中最核心的算法即patch,patch算法的核心是diff算法,它能够判断出哪些节点发生了变化,从而只对发生了变化的节点进行更新操做。

diff算法

image.png
Vue的diff算法是基于snabbdom改造过来的,仅在同级的vnode间作diff,递归地进行同级vnode的diff,最终实现整个DOM树的更新。由于跨层级的操做是很是少的,忽略不计,这样时间复杂度就从O(n3)变成O(n)。

diff 算法包括几个步骤:bash

  • 用 JavaScript 对象结构表示 DOM 树的结构;而后用这个树构建一个真正的 DOM 树,插到文档当中
  • 当状态变动的时候,从新构造一棵新的对象树。而后用新的树和旧的树进行比较,记录两棵树差别
  • 把所记录的差别应用到所构建的真正的DOM树上,视图就更新了
    image.png

diff算法的实现过程

diff 算法自己很是复杂,实现难度很大。本文去繁就简,粗略介绍如下两个核心函数实现流程:前端框架

  • patch(container,vnode) :初次渲染的时候,将vnode渲染成真正的DOM而后插入到容器里面。
  • patch(vnode,newVnode):再次渲染的时候,将新的vnode和旧的vnode相对比,而后之间差别应用到所构建的真正的DOM树上。

patch(container,vnode)

经过这个函数可让vnode渲染成真正的DOM,咱们经过如下模拟代码,能够了解大体过程:app

function createElement(vnode) {    
var tag = vnode.tag  
var attrs = vnode.attrs || {}    
var children = vnode.children || []    
if (!tag) {       
 return null  
  }    
// 建立真实的 DOM 元素    
var elem = document.createElement(tag)   
 // 属性    
var attrName    
for (attrName in attrs) {    
    if (attrs.hasOwnProperty(attrName)) { 
           // 给 elem 添加属性
           elem.setAttribute(attrName, attrs[attrName])
        }
    }
    // 子元素
    children.forEach(function (childVnode) {
        // 给 elem 添加子元素,若是还有子节点,则递归的生成子节点。
        elem.appendChild(createElement(childVnode))  // 递归
    })    // 返回真实的 DOM 元素   
 return elem
}
复制代码

patch(vnode,newVnode)

这里咱们只考虑vnode与newVnode如何对比的状况:

function updateChildren(vnode, newVnode) {
    var children = vnode.children || []
    var newChildren = newVnode.children || []
  // 遍历现有的children
    children.forEach(function (childVnode, index) {
        var newChildVnode = newChildren[index]
  // 二者tag同样
        if (childVnode.tag === newChildVnode.tag) {
            // 深层次对比,递归
            updateChildren(childVnode, newChildVnode)
        } else { 
  // 二者tag不同
           replaceNode(childVnode, newChildVnode) 
       }
    }
)}
复制代码

Virtual DOM的优点

  • 具有跨平台的优点 因为 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,因此使它具备了跨平台的能力,好比说浏览器平台、Weex、Node 等。

  • 操做 DOM 慢,js运行效率高。咱们能够将DOM对比操做放在JS层,提升效率。 由于DOM操做的执行速度远不如Javascript的运算速度快,所以,把大量的DOM操做搬运到Javascript中,运用patching算法来计算出真正须要更新的节点,最大限度地减小DOM操做,从而显著提升性能。

  • 提高渲染性能 Virtual DOM的优点不在于单次的操做,而是在大量、频繁的数据更新下,可以对视图进行合理、高效的更新。

总结:Vue.js经过编译将模版转换成渲染函数(render),执行渲染函数就能够获得一个虚拟节点树(虚拟DOM),虚拟节点树(虚拟DOM)提供虚拟节点vnode和对新旧两个vnode进行比对并根据比对结果进行DOM操做来更新视图,最大限度减小对DOM操做,从而减小浏览器的开销,提升渲染速度,改善用户体验。

参考文章: github.com/ljianshu/Bl…

book.douban.com/subject/325…

相关文章
相关标签/搜索