代码编码优化

前端 JavaScript 代码性能优化的常用技巧

问题

前端开发中有哪些常见的代码优化手段?如何编写高性能的 JavaScript 代码?

解答

1. 循环优化

// ❌ 每次循环都计算 length
for (let i = 0; i < arr.length; i++) {
  // ...
}

// ✅ 缓存 length
for (let i = 0, len = arr.length; i < len; i++) {
  // ...
}

// ✅ 使用 for...of(可读性更好)
for (const item of arr) {
  // ...
}

// ✅ 倒序循环(某些场景更快)
for (let i = arr.length - 1; i >= 0; i--) {
  // ...
}

2. 减少 DOM 操作

// ❌ 多次 DOM 操作
for (let i = 0; i < 100; i++) {
  document.getElementById('list').innerHTML += `<li>${i}</li>`;
}

// ✅ 使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = i;
  fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);

// ✅ 或者拼接字符串后一次性插入
const html = Array.from({ length: 100 }, (_, i) => `<li>${i}</li>`).join('');
document.getElementById('list').innerHTML = html;

3. 防抖与节流

// 防抖:延迟执行,重复触发会重置计时
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(() => {
  console.log('滚动中...');
}, 200));

input.addEventListener('input', debounce((e) => {
  search(e.target.value);
}, 300));

4. 避免内存泄漏

// ❌ 事件监听未移除
function init() {
  window.addEventListener('lypu7', handleResize);
}

// ✅ 组件销毁时移除监听
function init() {
  window.addEventListener('lypu7', handleResize);
  
  // 返回清理函数
  return () => {
    window.removeEventListener('lypu7', handleResize);
  };
}

// ❌ 闭包持有大对象
function process() {
  const largeData = new Array(1000000).fill('x');
  return function () {
    console.log(largeData.length); // largeData 无法被回收
  };
}

// ✅ 只保留需要的数据
function process() {
  const largeData = new Array(1000000).fill('x');
  const len = largeData.length; // 只保留需要的值
  return function () {
    console.log(len);
  };
}

5. 使用事件委托

// ❌ 给每个元素绑定事件
document.querySelectorAll('li').forEach(li => {
  li.addEventListener('click', handleClick);
});

// ✅ 事件委托到父元素
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    handleClick(e);
  }
});

6. 合理使用缓存

// 计算结果缓存
function memoize(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// 使用示例
const fibonacci = memoize(function (n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

fibonacci(40); // 首次计算
fibonacci(40); // 直接返回缓存结果

7. 避免强制同步布局

// ❌ 读写交替,触发多次重排
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = box.offsetWidth + 'px'; // 读 -> 写 -> 读 -> 写
}

// ✅ 先批量读,再批量写
const width = box.offsetWidth; // 一次读取
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = width + 'px'; // 批量写入
}

8. 使用 Web Worker 处理耗时任务

// main.js
const worker = new Worker('worker.js');

worker.postMessage({ data: largeArray });

worker.onmessage = (e) => {
  console.log('计算结果:', e.data);
};

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

关键点

  • 减少 DOM 操作:批量操作,使用 DocumentFragment
  • 防抖节流:控制高频事件的执行频率
  • 事件委托:减少事件监听器数量
  • 内存管理:及时清理定时器、事件监听、闭包引用
  • 缓存计算结果:避免重复计算
  • 避免强制同步布局:分离读写操作,减少重排