虚拟节点与DOM Diff算法
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 function render (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