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