javascript性能如何优化_有哪些常见技巧【教程】

JavaScript性能优化需围绕执行效率、内存占用和响应及时性做取舍,核心是避免主线程阻塞、减少GC压力、按需加载及测量先行。

JavaScript 性能优化不是“加个 use strict 就快了”,而是围绕执行效率、内存占用和响应及时性做有针对性的取舍。多数卡顿问题出在主线程被长时间阻塞,或无意中创建了大量临时对象。

避免长任务阻塞主线程

浏览器主线程既要处理 JS 执行,也要负责渲染、响应事件。一个超过 50ms 的同步任务(比如遍历 10 万条数据并频繁 DOM 操作)就会导致掉帧甚至卡死。

  • setTimeoutrequestIdleCallback 拆分耗时逻辑,让出控制权给渲染
  • 对大数组遍历,改用 for 循环而非 forEach(避免函数调用开销和闭包捕获)
  • DOM 批量操作优先使用 DocumentFragment 或一次性设置 innerHTML,而不是反复 appendChild

减少垃圾回收压力

V8 的垃圾回收(GC)虽快,但频繁触发仍会导致明显停顿。常见诱因是短生命周期对象暴增,比如在循环里反复创建对象或字符串拼接。

  • 复用对象:把可重用的配置对象、工具实例提成模块级变量,避免每次调用都 new 一个
  • 字符串拼接优先用数组 join 或模板字面量,少用 +=(尤其在循环中)
  • 监听器不及时移除会隐式持有 DOM 引用,造成内存泄漏;用 addEventListener 时尽量传入 { once: true }

按需加载与代码分割

首屏不需要的逻辑,就别让它进主包。体积大 ≠ 运行慢,但体积大会拖慢解析、编译和首次执行时间。

  • dynamic import() 实现路由级或组件级懒加载,Webpack/Vite 会自动切 chunk
  • 第三方库只导入需要的函数,比如从 lodash-es 中解

    debounce,而非整个 lodash
  • 避免在模块顶层执行复杂初始化(如预计算大数组),改到真正用到时再惰性求值

警惕隐藏的性能陷阱

有些写法看着干净,实则代价很高,且不易被察觉。

  • console.log 在生产环境未关闭时,可能序列化大型对象并阻塞主线程
  • for...in 遍历对象会遍历原型链,应优先用 Object.keys(obj).forEachfor...of + Object.entries
  • 频繁读写 offsetHeightgetBoundingClientRect() 会强制触发回流(reflow),应缓存结果或用 ResizeObserver/IntersectionObserver 替代轮询
const expensiveList = new Array(100000).fill(0);
// ❌ 卡住主线程
expensiveList.forEach((_, i) => {
  document.body.innerHTML += `${i}`;
});

// ✅ 拆分 + 批量插入
function renderChunk(start, end) {
  const fragment = document.createDocumentFragment();
  for (let i = start; i < end && i < expensiveList.length; i++) {
    const div = document.createElement('div');
    div.textContent = i;
    fragment.appendChild(div);
  }
  document.body.appendChild(fragment);
}
renderChunk(0, 1000); // 第一批
setTimeout(() => renderChunk(1000, 2000), 0);

最常被忽略的是「测量先行」——不靠直觉,用 Chrome DevTools 的 Performance 面板录制真实用户操作路径,看火焰图里哪一帧耗时异常、GC 是否密集、是否有 layout thrashing。没有 profile 数据的优化,大概率是在改错地方。