高性能 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
  • 按需加载:使用懒加载和虚拟列表处理大量数据