美文网首页前端性能优化
解析vue框架中的diff算法

解析vue框架中的diff算法

作者: 祝家庄打烊 | 来源:发表于2020-09-24 18:20 被阅读0次

    大致思路是这样的,真实DOM树和虚拟DOM树进行比较,差异的部分通过对象进行存储起来,生成一个patch补丁包。等到下一渲染的时候,对比补丁包的内容进行DOM操作。这样大大节省渲染速度,提高了性能。

    创建两个对象,模拟DOM树

    var createElement1 = common.createElement("div",{class:"vitual"},[
        common.createElement("p",{class:"vitual-p"},["vitual"]),
        common.createElement("p",{class:"vitual-p"},["vitual"]),
        common.createElement("p",{class:"vitual-p"},["vitual"]),
        common.createElement("p",{class:"vitual-p"},["vitual"]),
    ]);
    var createElement2 = common.createElement("div",{class:"vitual",style:"font-size:16px"},[
        common.createElement("p",{class:"vitual-p"},["vitual123"]),
        common.createElement("p",{class:"vitual-p"},["vitual"]),
        common.createElement("p",{class:"vitual-p"},["vitual"])
    ]);
    

    返回对象的形式进行比较,type存放的元素名称,props存放的属性,children存放的子节点

    createElement(type,props,children){
        return {type,props,children}
    }
    

    渲染成真实的DOM

    var docDom = common.render(createElement1);
    

    创建元素,修改元素的属性,遍历子节点,子节点不是字符串,递归继续执行render,是则创建文本节点,输出返回

    render(res){
        var createDom = document.createElement(res.type);
        Object.keys(res.props).forEach(key=>{
            this.setAttr(createDom,key,res.props[key])
        });
        res.children && res.children.forEach(item=>{
            var createEle = typeof(item)=="string"?document.createTextNode(item):this.render(item);
            createDom.appendChild(createEle)
        })
        return createDom;
    },
    setAttr(dom,key,value){
        var domName = dom.tagName.toUpperCase();
        switch(key){
            case "value":
                if(domName=="INPUT" && domName=="TEXTAREA"){
                    dom.value = value;
                }else{
                    dom.setAttribute(key,value);
                };
                break;
            case "style":
                dom.style.cssText = value;
                break;
            default:
                dom.setAttribute(key,value);
        }
        return dom;
    }
    

    获取旧节点并渲染到body中

    var docDom = common.render(createElement1);
    el.appendChild(docDom);
    

    diff算法,比较两个虚拟DOM的差异,也可以是虚拟DOM和真实DOM的差异

    var diff = common.diff(createElement1,createElement2);
    

    创建补丁包,补丁包的格式为 {0:[{type:"REMOVE",content:"value"}]}。新对象不存,说明DOM元素已经被删除。新老元素的类型为字符串,说明DOM是文本节点。新老对象类型相同,可能元素属性发现变化,进行下一步比较,遍历子节点比较,记录每个节点的索引。

    patcher:{},
    index:0,
    diff(oldEl,newEl){
        var patch = [];
        if(!newEl){
            patch.push({type:"REMOVE",content:this.index});
            this.patcher[this.index] = patch;
        }else if(typeof(oldEl)=="string" && typeof(newEl)=="string"){
            if(oldEl!=newEl){
                patch.push({type:"TEXT",content:newEl});
                this.patcher[this.index] = patch;
            }
        }else if(oldEl.type==newEl.type){
            var compareAttr = this.compareAttr(oldEl.props, newEl.props);
            if(Object.keys(compareAttr).length>0){
                patch.push({type:"ATTR",content:compareAttr});
                this.patcher[this.index] = patch;
            }
            oldEl.children && oldEl.children.forEach((item,index)=>{
                this.index++;
                this.diff(item,newEl.children[index])
            })
        }else{
            patch.push({type:"REPLACE",content:newEl});
            this.patcher[this.index] = patch;
        }
        return this.patcher;
    },
    compareAttr(oldAttr,newAttr){
        var reAttr = {};
        Object.keys(oldAttr).forEach(key=>{
            if(oldAttr[key]!=newAttr[key]){
                reAttr[key] = newAttr[key]
            }
        });
        Object.keys(newAttr).forEach(key=>{
            if(!oldAttr.hasOwnProperty(key)){
                reAttr[key] = newAttr[key]
            }
        });
        return reAttr;
    }
    

    补丁包渲染到页面上

    common.update(el,diff);
    

    重新根据DOM节点记录索引,遍历子节点对比每个子节点在补丁包发生哪些变化,从而操作DOM元素

    updateIdx:0,
    update(el,patch){
        let childNode =  el.childNodes;
        childNode && childNode.forEach(item=>{
            this.loadDom(item,patch)
            this.updateIdx++;
            this.update(item,patch)
        })
    },
    loadDom(dom,patch){
        patch[this.updateIdx] && patch[this.updateIdx].forEach(item=>{
            switch(item.type){
                case "REMOVE":
                    dom.parentNode.removeChild(dom);
                    break;
                case "TEXT":
                    dom.textContent = item.content;
                    break;
                case "ATTR":
                    Object.keys(item.content).forEach(key=>{
                        if(item.content[key]){
                            this.setAttr(dom,key,item.content[key])
                        }else{
                            dom.removeAttribute(key);
                        }
                    })
                    break;
                case "REPLACE":
                    let newDom = item.content.type?this.render(item.content):document.createTextNode(item.content);
                    dom.parentNode.replaceChild(newDom,dom);
                    break
            }
        })
    }
    

    相关文章

      网友评论

        本文标题:解析vue框架中的diff算法

        本文链接:https://www.haomeiwen.com/subject/naktuktx.html