来了解下我理解的~DOM DIFF

789 阅读3分钟

DOM DIFF

通过JS层面的计算(逐层比对虚拟DOM对象),生成patch对象(即补丁对象),然后对补丁进行解析和重新渲染

  • 对树进行分层比较,两棵树只会对同一层次的节点进行比较(不会跨节点比较)

    4.png

  • React 只会简单的考虑同层级节点的位置变换,而对于不同层级的节点,只有创建和删除操作

4.png

可以处理同级节点顺序的改变

  • 每一层级进行对比的计算法则:深度优先遍历
  • 节点类型相同,看属性是否相同,如果不同,则产生属性的补丁包 {type:’ATTRS’,attrs:{class:’BB’}}
  • 新的DOM节点不存在,被删除了{type:’REMOVE’,index:xxx}
  • 节点类型不相同,直接替换即可 {type:’REPLACE’,newNode:xxx}
  • 文本的内容进行变化 {type:’TEXT’,text:xxx}

function diff(oldTree,newTree){
    let patchs = {}; //=>补丁包 按照层级放置补丁包 {1:[],2:[]}
    let index = 0; //=>比较的层级
    //=>递归树,比较后的结果放到补丁包
    walk(oldTree,newTree,index,patchs);
    return patchs;
}

function walk(oldNode,newNode,index,patchs){
    let currentPatch={};
    if(!newNode){
        //=>新元素不存在,代表删除
        currentPatch.push({
            type:'EMOVE',
            index
        });
    }else if(typeof oldNode==="string"&&typeof newNode==="string"){
        //=>如果是文本,判断文本是否改变 
        if(oldNode!==newNode){
            currentPatch.push({
                type:'TEXT',
                text:newNode
            });
        }
    }else if(oldNode.type===newNode.type){
        //=>比较属性是否有更改
        let attrs=diffAttr(oldNode.props,newNode.props);,
        if(Object.keys(attrs).length>0){
            currentPatch.push({
                type:'ATTRS',
                attrs
            });
        }
        //=>如果有儿子节点,则遍历儿子
        diffChildren(
            oldNode.props.childrren,
            newNode.props.childrren,
        index,patchs);
    }else{
        //=>节点被替换了
        currentPatch.push({
            type:'REPACE',
            newNode
        });
    }
    //=>当前本级查找,确实有补丁,我们放到最外层补丁包中    
    if(currentPatch.length>0){
        patchs[index]=currentPatch;
    }
}

//=>比较儿子
function diffChildren(oldChildren,newChildren,index,patchs){
    oldChildrren.forEach((child,ind)=>{
        walk(child,newChildren[ind],++index,patchs);
    })
}   

//=>比较属性,生成补丁包
function diffAttr(oldAttrs,newAttrs){
    let patch={};
    for(ley key in oldAttrs){
        //=>属性不一样(可能是把老的中某个删除了,这样获取的结果可能是undefined)
        if(oldAttrs[key]!==newAttrs[key]){
            patch[key]=newAttrs[key];
        }
    }
    for(ley key in newAttrs){
       //=>看老的节点中是否有这样一个属性(没有就是新增)
       if(!oldAttrs.hasOwnProperty(key)){
            patch[key]=newAttrs[key];
        }
   }
    return patch;
}

根据补丁重新渲染

let patchs=diff(xxx,xxx); //=>两个虚拟DOM
let node;
let index=0;

walk(node);

functon walk(node){
    let currentPatch=patches[index++];
    let cildNodes=node.childNodes;
    cildNodes.forEach(child=>walk(child));
    if(currentPatch.length>0){
        doPatch(node,currentPatch);
    }
}

function doPatch(node,patch){
    patchs.forRach((item,index)=>{
        switch(patch.type){
            case 'ATTRS':
                for(let key in patch.attrs){
                    let val=patch.attrs[key];
                    if(val){
                        setAttr(node,key,val);
                    }else{
                        node.removeAttribute(key)
                    }
                }
                break;
            case 'TEXT':
                node.textContent=patch.text;
                break;
            case 'REPLACE':
                let newNode=(patch.newNode instanceof Element)?render(patch.newNode):document.createTextNode(patch.newNode);
                node.parentNode.replaceChild(newNode,node);
                break;
            case 'REMOVE':
                node.parentNode.removeChild(node);
                break;
        }
    })  
}

总结

  • 通过JS层面计算 === 对比的是虚拟DOM对象
  1. 第一次加载页面,所有的内容都要重新渲染(语法解析 -> 虚拟DOM -> 真实DOM -> 浏览器渲染) =>第一次加载页面越少渲染越好
  2. 上一次计算出来的虚拟DOM对象会存储起来,当状态或者其它数据改变,重新渲染组件(重新生成一套虚拟DOM对象)
  3. 把重新生成的虚拟DOM 和 之前存储起来的虚拟 DOM进行对比 =>把不一样的以补丁的形式存储起来(存储的还是对象)
  4. 重新渲染的过程,只是把补丁渲染到页面中,原有渲染过但是没有改变的东西是不需要处理的