面試加分-DOM DIFF

DOM DIFF

經過JS層面的計算(逐層比對虛擬DOM對象),生成patch對象(即補丁對象),而後對補丁進行解析和從新渲染node

  • 對樹進行分層比較,兩棵樹只會對同一層次的節點進行比較(不會跨節點比較) 算法

    4.png

  • React 只會簡單的考慮同層級節點的位置變換,而對於不一樣層級的節點,只有建立和刪除操做瀏覽器

4.png

能夠處理同級節點順序的改變

  • 每一層級進行對比的計算法則:深度優先遍歷
  • 節點類型相同,看屬性是否相同,若是不一樣,則產生屬性的補丁包 {type:’ATTRS’,attrs:{class:’BB’}}
  • 新的DOM節點不存在,被刪除了{type:’REMOVE’,index:xxx}
  • 節點類型不相同,直接替換便可 {type:’REPLACE’,newNode:xxx}
  • 文本的內容進行變化 {type:’TEXT’,text:xxx}

bash

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. 從新渲染的過程,只是把補丁渲染到頁面中,原有渲染過可是沒有改變的東西是不須要處理的
相關文章
相關標籤/搜索