js虚拟DOM-DIFF算法实现

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

  • 0
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术工厂 设计师:CSDN官方博客 返回首页
评论 4

打赏作者

灿尔哈擦苏

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值