前端性能优化

解析vue框架中的diff算法

2020-09-24  本文已影响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
        }
    })
}
上一篇下一篇

猜你喜欢

热点阅读