页面滚动卡顿?输入框响应慢?你点一下按钮它执行三次?
你怀疑是性能问题,其实可能只是JS 事件处理方式出了问题。
前端性能优化不仅是“把包压小”,更要保证代码执行流畅。
本篇文章聚焦 JS 执行性能,从节流、防抖、惰性执行三个角度,讲透它们的原理、使用场景与最佳实践。
一、JS 执行性能问题:都是事件惹的祸?
浏览器中,JS 是单线程运行的:
-
同一时刻只能做一件事
-
如果 JS 执行太慢,会卡住渲染与响应
常见卡顿场景:
|
场景 |
症状 |
|---|---|
|
滚动时绑定 onScroll,每像素触发一次 |
页面卡顿严重 |
|
输入框绑定 onInput 请求搜索 |
每敲一个字都发请求 |
|
页面 resize 时重新渲染 layout |
浏览器拉伸时卡到无法忍受 |
我们需要一个思路——别那么着急做事。
二、节流与防抖的区别?
节流(throttle):
规定一段时间内只触发一次
适用于:频繁触发但要“定时”执行的场景
比如:滚动监听、窗口 resize、mousemove
function throttle(fn, delay) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
调用方式:
window.addEventListener('scroll', throttle(handleScroll, 100));
防抖(debounce):
只在事件停止一段时间后才触发
适用于:用户操作完成后再执行的场景
比如:搜索框输入、表单验证、按钮防多点
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
调用方式:
input.addEventListener('input', debounce(handleInput, 300));
你应该记住这句话:
-
滚动/滑动类 → 节流
-
输入/点击类 → 防抖
-
实在不确定?先用防抖,再测体验
三、惰性执行:用的时候再做事,别一上来全做完
“惰性”,在性能优化里是种美德。
1. DOM 创建惰性化
不要初始化就渲染所有 tab 内容。等用户点了才加载:
function showTab(index) {
if (!document.querySelector(`#tab${index}`)) {
renderTab(index);
}
}
2. 模块懒加载(代码层面的惰性)
const loadEditor = () => import('./RichEditor.vue');
配合动态路由、条件渲染实现模块级性能控制。
四、现代方案推荐:lodash + requestIdleCallback
使用 lodash 封装好的方法:
import { debounce, throttle } from 'lodash-es';
element.addEventListener('scroll', throttle(doSomething, 100));
性能好 + 可读性强 + 有 cancel 控制
requestIdleCallback:让浏览器说“我空了你再来”
requestIdleCallback(() => {
// 比如你要执行统计上报/数据预加载等不重要任务
});
适合“用户不急,但我们想偷偷做”的场景。
注意兼容性:老浏览器需降级处理
五、JS 执行性能监控建议
使用 Chrome DevTools → Performance → Flame Chart:
-
查看函数执行耗时
-
分析主线程“是否被堵住”
-
哪段代码执行频率过高、是否频繁重排(Layout)
总结:写得快,不如执行得巧
性能不是“一次执行有多快”,而是“长期运行有多稳”。
记住:
-
节流是定期做事,防抖是停了再做事
-
惰性是别一股脑干完所有
-
优化执行效率 = 更少计算,更有节奏
你的页面不仅要打开得快,还要动起来也快。
1401

被折叠的 条评论
为什么被折叠?



