滚动监听必须用节流+闭包,因滚动事件高频触发易致卡顿,闭包可持久化保存previous时间戳和timeout状态,实现自包含、可复用、隔离的节流逻辑。

函数节流(Throttle)在滚动监听中,核心是用闭包保存上一次执行的时间戳或定时器状态,确保高频触发的 scroll 事件按固定间隔执行回调,避免性能损耗。
为什么滚动监听必须用节流 + 闭包?
滚动事件每秒可能触发数十次,若每次直接执行 DOM 操作或计算,极易造成页面卡顿。单纯用 setTimeout 或 setInterval 无法自动管理“上次执行时间”,而闭包能持久化记录这个关键状态,让节流逻辑自包含、可复用。
基础节流函数:闭包封装时间控制
以下是一个典型实现,利用闭包捕获 previous 时间变量和 timeout 引用:
function throttle(func, delay) {
let previous = 0;
let timeout = null;
<p>return function(...args) {
const now = Date.now();
const remaining = delay - (now - previous);</p><pre class="brush:php;toolbar:false;">if (remaining {
func.apply(this, args);
previous = Date.now();
timeout = null;
}, remaining);
}
}; }
-
previous在闭包中持续更新,记录上一次真正执行的时间 -
timeout也由闭包持有,用于清理待执行的延迟任务,防止重复设时 - 返回的匿名函数“记住”了外层作用域的所有变量,无需依赖全局或 this 绑定
实际滚动监听中的使用方式
将节流函数应用到 scroll 监听器中,只需一行包装:
const handleScroll = throttle(() => {
console.log('滚动位置:', window.pageYOffset);
// 执行懒加载、吸顶、视差等逻辑
}, 100); // 每100ms最多执行一次
<p>window.addEventListener('scroll', handleScroll);</p>- 每次滚动触发时,实际运行的是闭包包裹后的函数,它自行判断是否该执行
- 即使用户快速滚动,回调也不会堆积,也不会丢失“节奏感”
- 多个滚动监听器可各自独立调用
throttle,彼此状态不干扰——这正是闭包隔离性的体现
进阶:带取消能力的节流(支持 cleanup)
在单页应用中,组件卸载时需移除监听器。可扩展节流函数,暴露清除方法:
function throttle(func, delay) {
let previous = 0;
let timeout = null;
<p>const throttled = function(...args) {
const now = Date.now();
const remaining = delay - (now - previous);</p><pre class="brush:php;toolbar:false;">if (remaining {
func.apply(this, args);
previous = Date.now();
timeout = null;
}, remaining);
}
};
throttled.cancel = () => { if (timeout) { clearTimeout(timeout); timeout = null; } };
return throttled; }
-
throttled.cancel()可主动清空待执行的定时器,避免内存泄漏或异步回调误执行 - 在 React useEffect 或 Vue onUnmounted 中调用它,配合
removeEventListener更安全
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











