web-初探vue-diff算法1

背景

之所以要用diff算法,是因为渲染一棵真实的dom树的开销很大,比如修改某个结点,重排或者重绘dom树会消耗大量的时间,所以Vue采用diff算法来解决这个问题。
diff算法的本质是找出两个dom对象之间的差异,目的是尽可能的复用结点。这里的dom对象是vue中的virtual dom也就是虚拟dom,也就是说用js对象来表示页面中的dom结构。

这是真实的dom

<div id="app">
    <p id="app-child">
        this is a p
    </p>
</div>
复制代码

一棵dom树主要包括三个部分:

  1. 自身的标签名。div
  2. 自身的属性名 id=”app-child”
  3. 子节点 p

所以我们可以设计如下的对象结构表示:(伪代码)

这是虚拟的dom:

    let Vnode={
        tag:'div',
        children:[
            {
                tag:'p',
                innerText:'123'
            }
        ]
    }

复制代码

Vnode也是对象

如何比较?

在次啊用dom算法比较新旧结点的时候,比较只会在同一层级进行比较,也就是和兄弟,堂兄弟,不会对父子结点进行比较。

diff算法流程图

具体分析

path打补丁

function patch(oldVnode,vnode){
    if(sameVnode(oldVnode,vnode)){
        patchVnode(oldVnode,vnode)
    }else{
        const oe=oldVnode.e1   //当前oldVnode对应的真实父元素结点
        let parentElement=api.parentNode(oe)   //父元素
        createEle(vnode)   //根据Vnode生成新元素
        if(parentElement!=null){
            api.insertBefore(parentElement,vnode.e1,api.nextSibling(oe))   //将新元素添加进父元素中
            api.removeChild(parentElement,oldVnode.e1)  //移除以前的就元素结点
            oldVnode=null
        }
    }
}

复制代码

patch函数接收两个参数oldVnode和Vnode分别代表新的结点和旧的结点

判断两个结点是否值得比较,值得比较则执行patchVnode

    function sameVnode(a,b){
        return (
            a.key === b.key &&  // key值
            a.tag === b.tag &&  // 标签名
            a.isComment === b.isComment &&  // 是否为注释节点
            // 是否都定义了data,data包含一些具体信息,例如onclick , style
            isDef(a.data) === isDef(b.data) &&  
            sameInputType(a, b) // 当标签是<input>的时候,type必须相同
        )
    }
复制代码

如果子节点一样,那么就深入检查他们的子结点。因为diff是逐层比较,如果第一层不一样,就不会比较第二层了。(这样设计虽然存在一定的子节点一样的情况会导致不能重复利用,但是这样果断抛弃的可以减少错误)

patchVnode

    patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) 
    {
        api.setTextContent(el, vnode.text)
    }else 
    {
        updateEle(el, vnode, oldVnode)
        if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
        }else if (ch){
            createEle(vnode) //create el's children dom
        }else if (oldCh){
            api.removeChildren(el)
        }
    }
}

复制代码
  1. el为虚拟dom
  2. 判断Vnode和oldVnode是否指向同一个对象,如果是,直接return
  3. 如果他们都有text结点并且不相等,那么将el的文本结点设置为Vnode的文本结点。
  4. 如果oldVnode有子节点而Vnode没有,则将Vnode的子节点实例化之后添加到el
  5. 如果oldCh存在children结点把那个且oldCh不等于ch则执行updateChildren函数,else 如果chbu不为null,执行createEle()函数,添加vnode进去。如果oldCh部位null,则移除el
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享