vue虚拟dom,diff算法

123 阅读6分钟

1.什么是虚拟dom

虚拟dom本质上是js对象,是对真实dom的抽象,在状态变化时,记录新树和旧树的差异,最后把差异更新到真实dom中。

首先Virtual DOM是一个映射真实DOM的JavaScript对象,如果需要改变任何元素的状态,那么是先在Virtual DOM上进行改变,而不是直接改变真实的DOM。当有变化产生时,一个新的Virtual DOM对象会被创建并计算新旧Virtual DOM之间的差别。之后这些差别会应用在真实的DOM上

    <div class='box'>
        <p class='item'>血糖</p>
        <strong>标题</strong>
    </div>

以上真实dom对应的虚拟dom如下:

    let vNode = {
        tagName:'div',
        props:{
            'class':'box'
        },
        children:[{
            tagName:'p',
            props:{
                'class':'item'
            },
            text:'血糖'
        },{
            tagName:'strong',
            props:{
                'class':'item'
            },
            text:'标题'
        }]
    }

假如我们改变了部分文本内容:

   <div class='box'>
        <p class='item'>血糖</p>
        <strong>修改的内容部分</strong>
    </div>
    新的虚拟dom就是:
     let vNode = {
        tagName:'div',
        props:{
            'class':'box'
        },
        children:[{
            tagName:'p',
            props:{
                'class':'item'
            },
            text:'血糖'
        },{
            tagName:'strong',
            props:{
                'class':'item'
            },
            text:'修改的内容部分'
        }]
    }

比较新旧2个虚拟dom,我们用一个算法叫diff算法,他主要是找出差异,最小化的更新视图,所以diff算法本质上就是比较2个js对象的差异。 diff算法内部流程:

b767688910ed0524124e3050f8f16c9.jpg 当数据改变的时候,就会触发我内部的setter方法,,进一步触发dep.notify方法,通知到各个数据使用方,执行patch方法。patch方法接收2个参数新旧虚拟节点。首先我在内部需要判断一下是不是同类标签,如果不是同类标签,那就没有比较的必要直接替换即可,如果是同一个标签,那就需要进一步执行patchVnode方法,在这个方法内部,也是首先需要判断一下新旧虚拟节点是否相等,如果相等就不用比较,直接return。如果不相等,那就需要分情况来对比,比对的原则就是以新虚拟节点的结果为准。第一种情况:新旧虚拟节点都有文本节点,直接用新的文本替换新的文本就行了。第二种情况:旧的虚拟dom没有子节点,新的虚拟dom有子节点,直接添加新的子节点。第三种:旧的有子节点,新的没有子节点,直接删除旧的子节点就行了。第四种:新旧都有子节点,我们就需要对比他们的子节点,所以我们内部封装了upadateChildren方法(首尾指针法),专门比对他们的子节点。

patchVnode

patchVnode(vNode,oldVnode)

snabbdom

要想了解虚拟dom,先了解以下snabbdom。snabbdom(github.com/snabbdom/sn…)

在这里看到官方给的一个example

image.png

这里可以看到列出来的两个主要的核心函数,即h()函数和patch()函数,我们先来看下h()函数:

h函数

image.png

可以看到创建的虚拟DOM树里面的结构在左边的vnode里都有体现,所以现在看来我们的虚拟DOM结构树和snabbdom中的h()函数是完全可以对应起来的,可以通过一个方法将虚拟DOM结构转化成vnode;而上图中newVnode则指的是虚拟DOM树中的数据发生变化之后生成的vnode

我们在回过头来看patch()函数

patch函数

patch函数的执行分为两个阶段,两次传递的参数都是两个

第一阶段为虚拟dom的第一次渲染,传递的两个参数分别是放真实DOM的container和生成的vnode,此时patch函数的作用是用来将初次生成的真实DOM结构挂载到指定的container上面。

第二阶段传递的两个参数分别为vnode和newVnode,此时patch函数的作用是使用diff算法对比两个参数的差异,进而更新参数变化的DOM节点

可以发现h函数和patch函数在snabbdom中实现vdom到真实DOM的转化起到了至关重要的作用,那么还有一个很重要的环节,patch函数中是怎么样实现对比两个vnode从而实现对真实DOM的更新的呢,这里还要提一下snabbdom的另外一个核心算法,即diff算法。

diff算法

但是此处是如何实现对vnode的对比的呢?参考以下代码

function updateChildren(vnode, newVnode) {      // 创建对比函数
      var children = vnode.children || []
      var newChildren = newVnode.children || []
 
     children.forEach(function(childrenVnode, index) {
         var newChildVnode = newChildren[index]  // 首先拿到对应新的节点
        if (childrenVnode.tag === newChildVnode.tag) {    // 判断节点是否相同
            updateChilren(childrenVnode, newChildVnode)   // 如果相同执行递归,深度对比节点
          } else {
             repleaseNode(childrenVnode, newChildVnode)    // 如果不同则将旧的节点替换成新的节点
       }
    })
}


function repleaseNode(vnode, newVnode) {    // 节点替换函数
   var elem = vnode.elem
    var newEle = createElement(newVnode)
  }

upadateChildren方法

4b605cce1b755b04cfd8b4c1cb09b20.jpg 有以上2个虚拟dom,进行同级比较,采用首尾指针法。 不管新旧虚拟dom,都有首尾2个元素。首先旧虚拟节点的start和新虚拟节点的的start比较,如果没有比对成功,旧虚拟节点的start和新虚拟dom的end比对,如果还没有成功,旧虚拟节点的start和新虚拟节点的start比对,如果还是没比对成功,旧虚拟节点的end和新虚拟节点的end比对。

2bd774830b842a39d8cae83638b63e3.jpg

2.虚拟dom的好处

虚拟dom将dom的对比放在了js层,通过对比不同之处来选择新的渲染节点,从而提高渲染效率。

用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。

借助snabbdom,vnode(老的虚拟节点),newVnode(新的虚拟节点),比较新旧借点是新增,删除,还是修改 好处:比较虚拟节点的变化,计算出一个最小的需要更新的视图,然后再去操作dom,没有变化的dom是不会重新渲染的

3.虚拟dom为什么能提高性能?

在使用的过程中,我们需要先将实体Dom转换成虚拟Dom,在处理完成之后,再将处理完成的虚拟Dom转化为实体Dom,这个过程中所消耗的性能也同样很多。但是为什么大家都说虚拟Dom比实体Dom快呢(或者说是虚拟Dom能够提高性能)?其实根本原因是就是使用了虚拟Dom能够减少实际Dom的操作次数,减少回流和重绘的次数

换句话说就是虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能

使用diff算法比较新旧虚拟DOM—-即比较两个js对象不怎么耗性能,而比较两个真实的DOM比较耗性能,从而虚拟DOM极大的提升了性能。

所以说为什么会使用虚拟Dom

减少不必要的重排和重绘(提高性能)

提高项目的可维护性(会降低部分性能,但是对于可维护性的角度来说,这部分性能的损耗是能够接受的)

4.diff算法(最小量更新)

以最小的代价对dom进行更新,本质就是比较两个js对象的差异。