JavaScript性能优化DOM操作优化实战
目录
JavaScript性能优化:DOM操作优化实战
JavaScript性能优化:DOM操作优化实战
一 重排与重绘的代价
问题场景
用户点击按钮后,需要动态生成一个包含10,000个选项的下拉列表,但界面出现长达5秒的冻结。
错误代码示例
function createList() {
const ul = document.getElementById("myList");
for(let i=0; i<10000; i++){
const li = document.createElement("li");
li.style.color = "red"; // 触发样式计算
li.style.margin = "2px"; // 触发布局
li.textContent = `选项 ${i}`;
ul.appendChild(li); // 每次循环都导致重排
}
}
问题分析
- 每次循环都修改元素样式 ➔ 触发 强制同步布局(Forced Synchronous Layout)
- 直接操作真实DOM ➔ 累计触发 10,000次重排
二 高效DOM操作三板斧
优化方案1:文档碎片(DocumentFragment)
function createListOptimized() {
const ul = document.getElementById("myList");
const fragment = document.createDocumentFragment(); // 内存中的虚拟容器
for(let i=0; i<10000; i++){
const li = document.createElement("li");
// 先完成所有属性设置
li.textContent = `选项 ${i}`;
li.className = "prestyled-item"; // 通过CSS类批量设置样式
fragment.appendChild(li); // 不会触发真实DOM操作
}
ul.appendChild(fragment); // 仅1次重排
}
优化方案2:样式批量处理
/* 将样式集中到CSS类 */
.prestyled-item {
color: red;
margin: 2px;
}
优化方案3:读写分离原则
// 错误:交替读写布局属性
element.style.width = "100px";
const height = element.offsetHeight; // 触发立即计算
element.style.height = height + "px";
// 正确:先读后写
const height = element.offsetHeight; // 集中读取
element.style.width = "100px"; // 集中写入
element.style.height = height + "px";
三 事件监听器的正确姿势
内存泄漏陷阱
// 错误:为每个列表项绑定监听器
document.querySelectorAll(".item").forEach(item => {
item.addEventListener("click", handleClick);
});
// 当需要移除元素时:
container.innerHTML = ""; // 监听器仍然残留在内存中!
优化方案:事件委托
// 正确:利用事件冒泡在父级监听
document.getElementById("container").addEventListener("click", e => {
if(e.target.classList.contains("item")) {
handleClick(e);
}
});
优势对比 :
方式 | 内存占用(1000个元素) | 初始化耗时 |
---|---|---|
单独绑定 | 1000个监听器 | 120ms |
事件委托 | 1个监听器 | 15ms |
四 虚拟DOM的启示
核心思想
数据变化
生成虚拟DOM树
对比新旧虚拟DOM
计算最小修改
批量更新真实DOM
手动实现简化版
let vDOM = { type: 'ul', children: [] };
// 更新函数
function updateDOM() {
const realDOM = document.createElement(vDOM.type);
vDOM.children.forEach(child => {
const node = document.createElement(child.type);
node.textContent = child.content;
realDOM.appendChild(node);
});
// 批量替换
document.getElementById("app").replaceChildren(realDOM);
}
// 添加新项时:
vDOM.children.push({ type: 'li', content: '新项目' });
requestAnimationFrame(updateDOM); // 在下一次重绘时集中更新
本章重点总结
- 批量操作 :使用文档碎片减少DOM操作次数
- 样式管理 :用CSS类替代直接样式操作
- 事件优化 :委托模式节省内存
- 更新策略 :集中修改减少重排次数