节流函数必须用闭包维持lastTime或timer等跨调用状态,核心是通过闭包“记住”上次执行时机以限制高频调用;时间戳版立即执行但丢弃尾部,定时器版首执立即后续周期执行,增强版支持leading/trailing配置。

节流函数(Throttle)通过闭包保存上一次执行的时间戳或定时器状态,从而限制函数在指定时间间隔内最多执行一次。核心在于用闭包“记住”上次触发的时机,避免高频调用。
用时间戳实现节流(闭包保存 lastTime)
每次调用时对比当前时间与上次执行时间,只有超过设定间隔才执行,并更新 lastTime:
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}- 闭包捕获并维护 lastTime 变量,各次调用共享同一份状态
- 适合鼠标移动、窗口缩放等需“按固定间隔取样”的场景
- 缺点:最后一次触发若不足 delay,将被丢弃
用定时器实现节流(闭包保存 timer)
确保至少执行一次(首次立即执行),之后按 delay 周期性执行,通过 clearTimeout 防止重复设定时器:
function throttle(func, delay) {
let timer = null;
return function(...args) {
if (!timer) {
func.apply(this, args); // 立即执行第一次
timer = setTimeout(() => {
timer = null;
}, delay);
}
};
}- 闭包维持 timer 引用,控制定时器的创建与清除
- 更平滑,适合需要“首次响应快、后续稳定节制”的交互(如搜索框防抖+节流混合逻辑中的一环)
- 注意:该写法默认不支持“尾部执行”,如需结尾补一次,需额外标记和 cleanup
增强版:支持配置是否执行首尾(leading & trailing)
真实项目常用带选项的节流,仍靠闭包管理多个状态变量:
立即学习“Java免费学习笔记(深入)”;
function throttle(func, delay, options = {}) {
const { leading = true, trailing = false } = options;
let lastTime = 0;
let timer = null;
<p>const invoke = (...args) => func.apply(this, args);</p><p>return function(...args) {
const now = Date.now();</p><pre class="brush:php;toolbar:false;">if (!lastTime && !leading) lastTime = now;
if (now - lastTime >= delay) {
invoke(...args);
lastTime = now;
if (timer) {
clearTimeout(timer);
timer = null;
}
} else if (!timer && trailing) {
timer = setTimeout(() => {
invoke(...args);
lastTime = Date.now();
timer = null;
}, delay - (now - lastTime));
}}; }
- 闭包同时持有 lastTime、timer 和 options,封装完整控制逻辑
- leading 控制是否立即执行;trailing 控制是否在最后补一次(需配合 clearTimeout 重算延迟)
- 所有状态不暴露给外部,符合闭包的数据封装本质
为什么必须用闭包?
因为节流需要跨多次函数调用**维持中间状态**(比如上一次时间、定时器 ID)。普通函数每次执行都是全新作用域,无法记忆历史。闭包让返回的函数能持续访问外层函数的变量,形成私有、持久的状态容器——这正是节流可落地的关键机制。


















