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

JavaScript 性能优化不是“加个 use strict 就快了”,而是围绕执行效率、内存占用和响应及时性做有针对性的取舍。多数卡顿问题出在主线程被长时间阻塞,或无意中创建了大量临时对象。
避免长任务阻塞主线程
浏览器主线程既要处理 JS 执行,也要负责渲染、响应事件。一个超过 50ms 的同步任务(比如遍历 10 万条数据并频繁 DOM 操作)就会导致掉帧甚至卡死。
- 用
setTimeout或requestIdleCallback拆分耗时逻辑,让出控制权给渲染 - 对大数组遍历,改用
for循环而非forEach(避免函数调用开销和闭包捕获) - DOM 批量操作优先使用
DocumentFragment或一次性设置innerHTML,而不是反复appendChild
减少垃圾回收压力
V8 的垃圾回收(GC)虽快,但频繁触发仍会导致明显停顿。常见诱因是短生命周期对象暴增,比如在循环里反复创建对象或字符串拼接。
- 复用对象:把可重用的配置对象、工具实例提成模块级变量,避免每次调用都
new一个 - 字符串拼接优先用数组
join或模板字面量,少用+=(尤其在循环中) - 监听器不及时移除会隐式持有 DOM 引用,造成内存泄漏;用
addEventListener时尽量传入{ once: true }
按需加载与代码分割
首屏不需要的逻辑,就别让它进主包。体积大 ≠ 运行慢,但体积大会拖慢解析、编译和首次执行时间。
立即学习“Java免费学习笔记(深入)”;
- 用
dynamic import()实现路由级或组件级懒加载,Webpack/Vite 会自动切 chunk - 第三方库只导入需要的函数,比如从
lodash-es中解构debounce,而非整个lodash - 避免在模块顶层执行复杂初始化(如预计算大数组),改到真正用到时再惰性求值
警惕隐藏的性能陷阱
有些写法看着干净,实则代价很高,且不易被察觉。
-
console.log在生产环境未关闭时,可能序列化大型对象并阻塞主线程 -
for...in遍历对象会遍历原型链,应优先用Object.keys(obj).forEach或for...of+Object.entries - 频繁读写
offsetHeight、getBoundingClientRect()会强制触发回流(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 数据的优化,大概率是在改错地方。











