test.html:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>虚拟dom</title> 
</head> <body> <div id="root"></div> </body> </html> <script 
type="text/javascript" src="diff.js"></script> <script type="text/javascript" 
src="patch.js"></script> <script type="text/javascript"> //虚拟dom的类 class 
Element{ constructor(type, props, children){ this.type = type; this.props = 
props; this.children = children; } } //返回虚拟节点 function createElement(type, 
props, children){ return new Element(type, props, children) } //设置属性 function 
setAttr(node, key, value){ switch(key){ case 'value': //node是一个输入框 
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; } } //插入到页面 function renderDom(el, 
target){ target.appendChild(el) } //转为节点dom function render(eleObj){ let el = 
document.createElement(eleObj.type); for(let key in eleObj.props){ //设置属性的方法 
setAttr(el, key, eleObj.props[key]) } //有子节点 eleObj.children.forEach(child => { 
child = (child instanceof Element) ? render(child) : 
document.createTextNode(child); el.appendChild(child) }) return el } 
//----------------------------------------- //虚拟dom let vertualDom = 
createElement('ul',{class:'list'},[ createElement('li',{class: 'item'}, ['a']), 
createElement('li',{class: 'item'}, ['b']) ]) let vertualDom1 = 
createElement('ul',{class:'list11'},[ createElement('li',{class: 'item'}, 
['a']), createElement('p',{class: 'item11'}, ['b1']) ]) //将虚拟dom转为真实dom渲染到页面 
let el = render(vertualDom) renderDom(el, document.getElementById('root')) 
//修改的补丁 let patches = diff(vertualDom, vertualDom1) console.log(patches) 
//重新更新视图 patch(el, patches) </script> 
difff.js:
const ATTRS = 'ATTRS'; const TEXT = 'TEXT'; const REMOVE = 'REMOVE'; const 
REPLACE = 'REPLACE'; let Index = 0; function diff(oldTree, newTree){ let 
patches = {}; //补丁 let index = 0; //开始比较的节点 //递归树 比较后的结果放到补丁包中 walk(oldTree, 
newTree, index, patches) return patches; } //属性比较 function diffAttr(oldAttrs, 
newAttrs){ let patch = {} //新旧属性对比 for(let key in oldAttrs){ if(oldAttrs[key] 
!== newAttrs[key]){ patch[key] = newAttrs[key] } } for(let key in newAttrs){ 
//老节点没有新节点的属性 if(!oldAttrs.hasOwnProperty(key)){ patch[key] = newAttrs[key] } } 
return patch; } function walk(oldNode, newNode, index, patches){ //自己的补丁包 let 
currentPatch = [] //节点删除 if(!newNode){ currentPatch.push({ type: REMOVE, index 
}) }else if(isString(oldNode) && isString(newNode)){//字符串 //文本不一致修改为最新的 
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.children, newNode.children, patches); }else{ 
//节点被替换 currentPatch.push({ type: REPLACE, newNode }) } //有补丁 
if(currentPatch.length > 0){ //将元素和补丁对应放到外面的大补丁中去 patches[index] = currentPatch 
} } function diffChildren(oldChildren, newChildren, patches){ 
oldChildren.forEach((child, idx)=>{ //索引全局 index walk(child, newChildren[idx], 
++Index, patches) }) } function isString(node){ return 
Object.prototype.toString.call(node) === "[object String]" } 
patch.js:
let allPatches; let index = 0; //需要补丁的索引 function patch(node, patches){ 
allPatches = patches patchWalk(node) } function patchWalk(node){ let 
currentPatch = allPatches[index++] let childNodes = node.childNodes; 
childNodes.forEach(child=>{ patchWalk(child) }) //有补丁 if(currentPatch){ 
doPatch(node, currentPatch) } } //给对应节点对应补丁 function doPatch(node, patches){ 
patches.forEach(item => { switch(item.type){ case 'ATTRS': for(let key in 
item.attrs){ let val = item.attrs[key] if(val){ setAttr(node, key, val) }else{ 
node.removeAttribute(key) } } break; case 'TEXT': console.log(item.text) 
node.textContent = item.text break; case 'REPLACE': let newNode = (item.newNode 
instanceof Element) ? render(item.newNode) : 
document.createTextNode(item.newNode); node.parentNode.replaceChild(newNode, 
node) break; case 'REMOVE': break; default: break; } }) }