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; } }) }