React DOM diff

virtul DOM 也就是虛擬節(jié)點(diǎn)。通過JS的Object對(duì)象模擬DOM中的真實(shí)節(jié)點(diǎn)對(duì)象,再通過特定的render方法將其渲染成真實(shí)的DOM節(jié)點(diǎn)。

一、渲染步驟

生成vNode---->渲染成真實(shí)節(jié)點(diǎn) --------->掛載到頁面--------->diff比較
1、模擬方法和渲染方法
需求,生成一個(gè)如下圖所示的DOM結(jié)構(gòu)


image.png

調(diào)用

let virtualDom1 = createElement('ul', {class: 'list'}, [
    createElement('li', {class: 'item'}, ['a']),
    createElement('li', {class: 'item'}, ['b']),
    createElement('li', {class: 'item'}, ['c']),
])
let virtualDom2 = createElement('ul', {class: 'list'}, [
    createElement('li', {class: 'item'}, ['1']),
    createElement('li', {class: 'item'}, ['2']),
    createElement('li', {class: 'item'}, ['3']),
])
let el = render(virtualDom);
renderDom(el, window.root);
let patchs = diff(virtualDom1, virtualDom2);

生成虛擬對(duì)象的方法createElement

function createElement(type, props, children) {
    return new Element(type, props, children)
}
class Element{
    constructor(type, props, children){
        this.type = type;
        this.props = props;
        this.children = children
    }
}

將虛擬對(duì)象渲染成真實(shí)DOM的render方法

//render方法將vNode轉(zhuǎn)化成真實(shí)DOM
function render(eleObj){
    //創(chuàng)建元素
    let el = document.createElement(eleObj.type);
    //設(shè)置屬性
    for(let key in eleObj.props) {
        setAttr(el, key, eleObj.props[key]);
    }
    //遞歸渲染子元素
    eleObj.children.foEach(child => {
        child = child instanceof Element ? render(child) : document.createTextNode(child);
        el.appendChild(child);
    })
}
setAttr(node, key, value) {
    switch(key) {
        case 'value':
            if (node.tagName.toUpperCase() === 'INPUT' || node.tagName.toUpperCase() === 'TEXTAREA') {
                node.value = value;          
            }else {
                node.setAttribute(key, value);
            }
            break;
        case 'style':
            node.style.cssText = value;
            break;
        default:
            node.setAttribute(key, value);
            break;
    }
}

渲染節(jié)點(diǎn)到頁面的方法renderDom

//將真實(shí)DOM渲染到頁面
function renderDom(el, target) {
    target.appendChild(el);
}

二、DOM DIFF 算法

1、核心思想

DOM DIFF 就是比較兩個(gè)虛擬DOM的區(qū)別,實(shí)際上就是比較兩個(gè)對(duì)象的區(qū)別。根據(jù)兩個(gè)虛擬對(duì)象創(chuàng)建出補(bǔ)丁,描述改變的內(nèi)容。將這個(gè)補(bǔ)丁用來更新DOM。


image.png

【注意】不會(huì)更改所有節(jié)點(diǎn),只更改有改變的部分

2、DOM DIFF 兩種優(yōu)化策略

1)分層比較,一層一層比,不會(huì)跨級(jí)對(duì)比
2)如果一層的對(duì)象只是換了下位置,可以通過key值直接換位置。

2、算法實(shí)現(xiàn)

差異計(jì)算:先序深度優(yōu)先遍歷


image.png

規(guī)則:
1、若節(jié)點(diǎn)類型不相同,直接采用替換模式,{type:'REPLACE',newNode:newNode}
2、當(dāng)節(jié)點(diǎn)類型相同時(shí),去看一下屬性是否相同,產(chǎn)生一個(gè)屬性的補(bǔ)丁包,比如{type:'ATTRS',attrs:{class: 'list-group'}
3、新的DOM節(jié)點(diǎn)不存在,也返回一個(gè)不存在的補(bǔ)丁包{type:'REMOVE',index:XXX}
4、文本的變化{type:'TEXT', text:1}

DIff 算法
//diff 算法
let Index = 0;
function diff(oldTree, newTree) {
    let patches = {};
    let index = 0;
    //遞歸數(shù)比較后的結(jié)果放到補(bǔ)丁包中
    walk(oldTree, newTree, index, patches);
    return patches;
}
function walk(oldTree, newTree, index, patches){
    let currentPatch = [];//每個(gè)元素都有一個(gè)補(bǔ)丁對(duì)象
    if (!newTree) {
        currentPatch.push({type:'REMOVE', index})
    } 
    if (isString(oldTree) && isString(newTree)) {
        // 判斷文本是否一致
        if (oldTree !== newTree) {
            currentPatch.push({type:'TEXT',text:newTree}); 
        }
    }else if(oldTree.type === newTree.type) {
        //比較屬性是否有更改
        let attrs = diffAttr(oldTree.props, newTree.props);
        if(Object.keys(attrs).length) {
            currentPatch.push({type:'ATTRS', attrs});
        }
        // 如果有兒子節(jié)點(diǎn),遍歷子節(jié)點(diǎn)
          diffChildren(oldTree.children, newTree.children, index, patches);
    } else {
        // 節(jié)點(diǎn)類型不同的時(shí)候,直接替換
        currentPatch.push({type:'REPLACE', newTree});
    }
    // 當(dāng)前元素有補(bǔ)丁的情況下,將元素和補(bǔ)丁對(duì)應(yīng)起來,放到大補(bǔ)丁包中
    if(currentPatch.length) {
        patches[index] = currentPatch; 
    }
}
function diffAttr(oldAttrs, newAttrs) {
    let patch = {};
    for(let key in oldAttrs) {
        if(oldAttrs[key] !== newAttrs[key]) {
            patch[key] = newAttrs[key];//有可能是undefined,新節(jié)點(diǎn)沒有舊節(jié)點(diǎn)的屬性      
        }
    }
    for(let key in newAttrs) {
        //老節(jié)點(diǎn)沒有新節(jié)點(diǎn)的屬性
        if(! oldAttrs.hasOwnProperty(key)) {
            patch[key] = newAttrs[key]
        }
    }
    return patch;
}

function diffChildren(oldChildren, newChildren, index, patches){
    // 比較老的第一個(gè)和新的第一個(gè)
    oldChildren.forEach((child, idx) => {
        // 記得索引得改
        // Index 每次傳遞給walk時(shí),index是遞增的,所有節(jié)點(diǎn)都基于一個(gè)序號(hào)實(shí)現(xiàn),因此需要維護(hù)一個(gè)全局Index
        walk(child, newChildren[idx], ++Index, patches);
    }) 
}


function isString(node) {
    return Object.prototype.toString.call(node) === '[object string]';
}


function patch(node, patches) {
 // 給某個(gè)元素打補(bǔ)丁
 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • virtul DOM 也就是虛擬節(jié)點(diǎn)。通過JS的Object對(duì)象模擬DOM中的真實(shí)節(jié)點(diǎn)對(duì)象,再通過特定的rende...
    飛飛廉閱讀 17,557評(píng)論 0 12
  • 參考文章:深度剖析:如何實(shí)現(xiàn)一個(gè)Virtual DOM 算法 作者:戴嘉華React中一個(gè)沒人能解釋清楚的問題——...
    waka閱讀 6,152評(píng)論 0 21
  • 1.為什么需要虛擬DOM DOM是很慢的,其元素非常龐大,頁面的性能問題由JS引起的,大部分都是由DOM操作引起的...
    Ecl_02b8閱讀 2,373評(píng)論 1 4
  • 在簡書的第二天吧算是,今天很開心。來參加一位大學(xué)同學(xué)的婚禮,我們在大學(xué)就是非常要好的朋友,雖然不在一個(gè)宿舍。我們的...
    HkingH閱讀 322評(píng)論 0 0
  • 一 “老梁,吃了沒?沒吃我這里還有一份客人沒吃多少的飯菜,就著也能吃吃,反正這么晚了總比你回家吃泡面強(qiáng)吧?”,老王...
    深深不是淺淺閱讀 411評(píng)論 0 18

友情鏈接更多精彩內(nèi)容