虛擬節點與DOM Diff演算法
阿新 • • 發佈:2022-04-06
DOM Diff
1.對比兩個虛擬節點,找出差異,再對應到真實DOM ,進行補丁;
由於DOM操作損耗效能,所以應求得最小代價
2.遵循結構上一一對應關係,索引值
3.p span 交換
4.Diff——深度優先遍歷
實現
createElement()方法 將虛擬節點變為物件
function createElement(type,props,children){ return new Element(type,props,children); }
虛擬節點變為真實節點 渲染為DOM結構
render()
/vDOM到rDOM functionrender (vDom){ const {type,props,children}=vDom, el = document.createElement(type); for( let key in props){ setAttrs( el,key, props[key]); } //console.log(el);
等同於
開始渲染DOM
renderDOM()
function renderDOM(rDom,rootEl){ rootEl.appendChild(rDom); }
domDiff.js
import { ATTR, TEXT, REPLACE, REMOVE }from '.patchTypes'; //從當前解構 //宣告 let patches ={}, vnIndex = 0; function domDiff(oldVDom,newVDom){ let index = 0; vNodeWalk(oldVDom,newVDom,index); //虛擬節點遍歷 return patches; } function vNodeWalk(oldNode,newNode,index){ let vnPatch= []; if(!newNode){ vnPatch.push({ type:REMOVE, index }) }else if(typeof oldNode === 'string' && typeof newNode === 'string'){ if(oldNode !== newNode){ vnPatch.push({ type:TEXT, text:newNode }) } } else if(oldNode.type === newNode.type){ const attrPatch = attrsWalk(oldNode.props,newNode.props); console.log(Object.keys(attrPatch));//一層 class if(Object.keys(attrPatch).length>0){ vnPatch.push({ type:ATTR, attrs:attrPatch }); } //遍歷子 childrenWalk(oldNode.children,newNode.children); } else{ vnPatch.push ({ type:REPLACE, newNode }); } //判斷是否有patch if(vnPatch.length>0){ patches[index] = vnPatch; } } //對比props function attrsWalk( oldAttrs,newAttrs){ let attrPatch={}; //修改屬性 for (let key in oldAttrs){ if(oldAttrs[key]!== newAttrs){ attrPatch[key] = newAttrs[key]; //打補丁 } } //是否有屬性 for (let key in newAttrs){ //遍歷新,對比舊判斷是否有 if(!oldAttrs.hasOwnProperty(key)){ attrPatch[key] = newAttrs[key]; } } return attrPatch; } function childrenWalk(oldChildren, newChildren){ oldChildren.map((c,idx) => { vNodeWalk(c,newChildren[idx], ++vnIndex) }) } export default domDiff;
實現打補丁
import { ATTR, TEXT, REPLACE, REMOVE } from './js/patchTypes'; import { setAttrs, render } from './js/virtualDom'; import Element from './js/Element'; let finalPatches = {}, rnIndex = 0; function doPatch (rDom, patches) { finalPatches = patches; rNodeWalk(rDom); } function rNodeWalk (rNode) { const rnPatch = finalPatches[rnIndex ++], childNodes = rNode.childNodes; [...childNodes].map((c) => { rNodeWalk(c); }); if (rnPatch) { patchAction(rNode, rnPatch); } } function patchAction (rNode, rnPatch) { rnPatch.map((p) => { switch (p.type) { case ATTR: for (let key in p.attrs) { const value = p.attrs[key]; if (value) { setAttrs(rNode, key, value); } else { rNode.removeAttribute(key); } } break; case TEXT: rNode.textContent = p.text; break; case REPLACE: const newNode = (p.newNode instanceof Element) ? render(p.newNode) : document.createTextNode(p.newNode); rNode.parentNode.replaceChild(newNode, rNode); break; case REMOVE: rNode.parentNode.removeChild(rNode); break; default: break; } }); } export default doPatch; // vNode = virtual Node // vnPatch = virtual Node patch // rNode = real Node // rnPatch = real Node patch