每天进步一点点

虚拟dom

啥是虚拟dom

为什么要有这玩意

这玩意给前端造成了那些影响

怎么做一个玩具版本的虚拟dom

虚拟dom, 听名字应该就知道了, 假dom, 为什么要有假dom, 因为操作真dom太重了吗?

都说虚拟dom能提高性能, 真的吗?

let dom = createElement('div')
div.style.background = 'red'
div.style.borderRadius = '50%'
div.style.width = '200px'
div.style.height = '200px'
div.innerHTML = 'hahha'

上面是一段操作dom的代码, 就是说你再怎么虚拟, 到最后你还是要执行上面这些代码, 相当于, 在在操作实际dom前, 先通过对比新旧虚拟dom, 计算出dom要做那些更新, 如果你是一个dom高手, 你完全可以通过自己的规划直接去操作达到最好,最优的操作, 如果你是dom 杀手, 那就不行了, 操作太多次会带来性能问题, 这么看虚拟dom, 通过付出计算新旧dom的代价做到了让dom新手写的程序也不那么费性能, 显然它并不是最优的

这段话应该解释了第一个,第二个问题.

继续

插一下, 那么怎么成为一个dom高手,根据我一年的前端开发经验,我知道下面这些高效操作dom的方式, 看看能不能帮到你

  1. 缓存, 基本上写过代码你应该就知道这个, 上到redis,下到let a = xx.value(后面用到xx.value, 直接用a,减少依赖收集), 所以缓存dom元素, 肯定也能减少性能开销
  2. 使用querySelectorquerySelectorAll选择元素。这些方法使用css选择器语法,可以快速定位特定的元素
  3. 使用DocumentFragment创建新的DOM元素。这允许在不触发dom重排的情况下创建和修改一组元素
  4. 使用事件委托来处理事件。这使得只需将事件处理程序绑定到祖先元素,而不需要将其绑定到每个后代元素上
  5. 将多个dom操作合并到单个批处理中。通过一次性更新dom树可以减少浏览器的重绘次数,并提高页面性能
  6. CSS样式应用到类而不是单个元素。这可以减少CSS选择器的数量,并减少浏览器的计算负担
  7. 避免使用innerHTML来插入HTML,因为它会破坏现有的事件处理程序和数据绑定。相反,使用DOM API来创建和插入新的DOM元素

上面是一个小插曲.

接着说虚拟dom对前端造成了什么影响, 从coding体验上来看, 显然是变爽了, 无论是vuer,reacter, 都享受到了这个便利, 太酷了, 从前端发展上, 好像有很大的里程碑意义, 我从事前端时间不长, 不过我读过一些历史, 从过去的jquery时代到现在大前端时代, 显然虚拟dom, 是一项很给力的技术.

那么如何做一个玩具版的虚拟dom

// 虚拟DOM
const map = (tag, attrs, children) => {
	return { tag, attrs, children };
};

// 实际DOM
const h = (node) => {
	if (typeof node === 'string') {
		return document.createTextNode(node);
	}
	const el = document.createElement(node.tag);
	node.children.forEach(child => {
		el.appendChild(h(child));
	});
	return el;
};

// 渲染DOM
const render = (root, node) => {
	root.appendChild(h(node));
};

// 更新DOM
const update = (oldNode, newNode) => {
	// 如果tag不同,直接替换为新节点
	if (oldNode.tag !== newNode.tag) {
		oldNode.el.replaceWith(h(newNode));
	} else {
		// 更新attrs
		const el = oldNode.el;
		const newAttrs = newNode.attrs;
		const oldAttrs = oldNode.attrs;
		Object.keys(newAttrs).forEach(key => {
			const newVal = newAttrs[key];
			const oldVal = oldAttrs[key];
			if (newVal !== oldVal) {
				el.setAttribute(key, newVal);
			}
		});
		Object.keys(oldAttrs).forEach(key => {
			if (!(key in newAttrs)) {
				el.removeAttribute(key);
			}
		});
		// 更新children
		const newChildren = newNode.children;
		const oldChildren = oldNode.children;
		if (typeof newChildren === 'string') {
			if (newChildren !== oldChildren) {
				el.textContent = newChildren;
			}
		} else {
			for (let i = 0; i < newChildren.length; i++) {
				update(oldChildren[i], newChildren[i]);
			}
			if (oldChildren.length > newChildren.length) {
				for (let i = newChildren.length; i < oldChildren.length; i++) {
					oldChildren[i].el.parentNode.removeChild(oldChildren[i].el);
				}
			} else if (oldChildren.length < newChildren.length) {
				for (let i = oldChildren.length; i < newChildren.length; i++) {
					const newChild = h(newChildren[i]);
					el.appendChild(newChild);
				}
			}
		}
		// 更新节点的元素引用
		newNode.el = oldNode.el;
	}
};

// 初始化虚拟DOM并挂载到页面
const rootNode = document.querySelector('#app');
const vNode = vdom('div', {}, [vdom('p', {}, ['Count: 0']), vdom('button', { onClick: increment }, 'Increment')]);
const root = render(rootNode, h(vNode));
vNode.el = root.firstChild;

let count = 0;

// 更新虚拟DOM并重绘视图
function increment () {
	const newVNode = vdom('div', {}, [vdom('p', {}, [`Count: ${++count}`]), vdom('button', { onClick: increment }, 'Increment')]);
	update(vNode, newVNode);
	vNode.el = newVNode.el;
}




以上是一个简易版本的虚拟dom,以及diff算法实现

posted on 2023-05-26 11:55  柯蓝僧人  阅读(9)  评论(0编辑  收藏  举报