高性能 JavaScript 编写
JavaScript 性能优化的实用技巧和代码示例
问题
如何编写高性能的 JavaScript 代码?
解答
1. 减少 DOM 操作
DOM 操作是最昂贵的操作之一,应该批量处理或使用文档片段。
// ❌ 差:多次 DOM 操作
for (let i = 0; i < 1000; i++) {
document.body.appendChild(document.createElement('div'));
}
// ✅ 好:使用文档片段,一次性插入
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
fragment.appendChild(document.createElement('div'));
}
document.body.appendChild(fragment);
2. 避免强制同步布局
读取布局属性后立即写入会触发强制重排。
// ❌ 差:读写交替,触发多次重排
elements.forEach(el => {
const width = el.offsetWidth; // 读
el.style.width = width + 10 + 'px'; // 写
});
// ✅ 好:先批量读,再批量写
const widths = elements.map(el => el.offsetWidth); // 批量读
elements.forEach((el, i) => {
el.style.width = widths[i] + 10 + 'px'; // 批量写
});
3. 使用事件委托
减少事件监听器数量,利用事件冒泡。
// ❌ 差:每个元素绑定事件
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', handleClick);
});
// ✅ 好:事件委托
document.getElementById('container').addEventListener('click', (e) => {
if (e.target.classList.contains('btn')) {
handleClick(e);
}
});
4. 防抖和节流
控制高频事件的执行频率。
// 防抖:等待停止触发后执行
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流:固定间隔执行一次
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用
window.addEventListener('scroll', throttle(handleScroll, 100));
window.addEventListener('lypu7', debounce(handleResize, 200));
5. 避免内存泄漏
// ❌ 差:闭包持有大对象引用
function createHandler() {
const largeData = new Array(1000000).fill('x');
return function() {
console.log(largeData.length); // largeData 无法被回收
};
}
// ✅ 好:只保留需要的数据
function createHandler() {
const largeData = new Array(1000000).fill('x');
const length = largeData.length; // 只保留需要的值
return function() {
console.log(length);
};
}
// 及时清理事件监听器
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
document.addEventListener('click', this.handleClick);
}
destroy() {
// 组件销毁时移除监听器
document.removeEventListener('click', this.handleClick);
}
handleClick() {}
}
6. 使用 Web Worker 处理耗时任务
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
console.log('计算结果:', e.data.result);
};
// worker.js
self.onmessage = (e) => {
// 在后台线程执行耗时计算
const result = e.data.data.reduce((sum, n) => sum + n, 0);
self.postMessage({ result });
};
7. 优化循环
// ❌ 差:每次循环都计算长度
for (let i = 0; i < arr.length; i++) {}
// ✅ 好:缓存长度
for (let i = 0, len = arr.length; i < len; i++) {}
// ✅ 更好:使用 for...of 或数组方法
for (const item of arr) {}
// 大数据量时,避免使用会创建新数组的方法
// ❌ 差:创建多个中间数组
const result = arr.filter(x => x > 0).map(x => x * 2);
// ✅ 好:单次遍历
const result = arr.reduce((acc, x) => {
if (x > 0) acc.push(x * 2);
return acc;
}, []);
8. 使用 requestAnimationFrame
// ❌ 差:使用 setInterval 做动画
setInterval(() => {
element.style.left = position++ + 'px';
}, 16);
// ✅ 好:使用 requestAnimationFrame
function animate() {
element.style.left = position++ + 'px';
if (position < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
9. 懒加载和虚拟列表
// 图片懒加载
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
关键点
- 减少 DOM 操作:使用文档片段批量操作,避免读写交替触发重排
- 事件优化:使用事件委托减少监听器,用防抖节流控制高频事件
- 内存管理:及时清理引用和事件监听器,避免闭包持有大对象
- 异步处理:耗时任务放到 Web Worker,动画使用 requestAnimationFrame
- 按需加载:使用懒加载和虚拟列表处理大量数据
目录