节流(Throttle)
节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
应用场景
- 滚动加载(滚动时每间隔一段时间检查位置)
- 按钮点击(防止重复提交)
- 鼠标移动(
mousemove事件) - 游戏中的按键操作
时间戳版本(立即执行)
javascript
function throttle(func, wait) {
let previous = 0;
return function () {
const now = Date.now();
const context = this;
const args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
};
}
定时器版本(延迟执行)
javascript
function throttle(func, wait) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}
综合版本(先立即执行,然后节流)
javascript
function throttle(func, wait) {
let timeout, context, args;
let previous = 0;
const later = function () {
previous = Date.now();
timeout = null;
func.apply(context, args);
};
const throttled = function () {
const now = Date.now();
// 下次触发 func 剩余的时间
const remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果没有剩余时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = null;
};
return throttled;
}
ES6版本
javascript
class Throttler {
constructor(func, wait, options = {}) {
this.func = func;
this.wait = wait;
this.options = {
leading: true, // 是否立即执行
trailing: true, // 是否在结束后再执行一次
};
Object.assign(this.options, options);
this.timeout = null;
this.previous = 0;
this.args = null;
this.context = null;
}
execute(...args) {
const now = Date.now();
this.context = this;
this.args = args;
// 如果是第一次调用且 leading 为 false, 设置 previous 为 now
if (!this.previous && this.options.leading === false) {
this.previous = now;
}
const remaining = this.wait - (now - this.previous);
// 时间已到货 remaining 异常(如系统时间被调整)
if (remaining <= 0 || remaining > this.wait) {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.previous = now;
this.func.apply(this.context, this.args);
}
// 如果 trailing 为 true, 且没有定时器
else if (!this.timeout && this.options.trailing !== false) {
this.timeout = setTimeout(() => {
this.previous = this.options.leading === false ? 0 : Date.now();
this.timeout = null;
this.func.apply(this.context, this.args);
}, remaining);
}
}
cancel() {
clearTimeout(this.timeout);
this.timeout = null;
this.previous = 0;
}
flush() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
this.func.apply(this.context, this.args);
}
}
}
// 节流使用 - 立即执行
const throttler1 = new Throttler((msg) => {
console.log("节流执行:", msg, Date.now());
}, 1000);
// 快速调用
throttler1.execute("队列消息1"); // 立即执行
throttler1.execute("队列消息2"); // 被忽略
setTimeout(() => throttler1.execute("队列消息3"), 1000); // 再次执行
// 节流使用 - 不立即执行
const throttler2 = new Throttler(
(msg) => console.log("节流执行:", msg, Date.now()),
1000,
{ leading: false, trailing: true }
);
// 手动取消
const throttler3 = new Throttler(() => {
console.log("执行中...");
}, 500);
// 开始执行
setInterval(() => throttler3.execute(), 100);
// 2秒后取消
setTimeout(() => {
throttler3.cancel();
console.log("已取消节流");
}, 2000);
更通用的类实现(同时支持防抖和节流)
javascript
class RateLimiter {
constructor(func, wait, options = {}) {
this.func = func;
this.wait = wait;
this.mode = options.mode || "throttle"; // 'throttle' 或 'debounce'
this.options = {
leading: true,
trailing: true,
maxWait: null, // 最大等待时间 (防抖专用)
};
Object.assign(this.options, options);
this.timeout = null;
this.previous = 0;
this.args = null;
this.context = null;
this.lastCallTime = 0;
}
execute(...args) {
const now = Date.now();
this.context = this;
this.args = args;
this.lastCallTime = now;
if (this.mode === "debounce") {
return this._debounce(now);
} else {
return this._throttle(now);
}
}
_debounce(now) {
if (this.timeout) clearTimeout(this.timeout);
// 计算延迟
let delay = this.wait;
// 检查是否需要立即执行
const callNow = this.options.leading && !this.timeout;
if (callNow) {
this.previous = now;
this.func.apply(this.context, this.args);
}
this.timeout = setTimeout(() => {
this.timeout = null;
if (!callNow || this.options.trailing) {
this.func.apply(this.context, this.args);
}
}, delay);
}
_throttle(now) {
if (!this.previous && this.options.leading === false) {
this.previous = now;
}
const remaining = this.wait - (now - this.previous);
if (remaining <= 0 || remaining > this.wait) {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.previous = now;
this.func.apply(this.context, this.args);
} else if (!this.timeout && this.options.trailing !== false) {
this.timeout = setTimeout(() => {
this.previous = this.options.leading === false ? 0 : Date.now();
this.timeout = null;
this.func.apply(this.context, this.args);
}, remaining);
}
}
cancel() {
clearTimeout(this.timeout);
this.timeout = null;
this.previous = 0;
}
flush() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
this.func.apply(this.context, this.args);
}
}
pending() {
return !!this.timeout;
}
}
// 使用示例
const limiter = new RateLimiter(
(msg) => console.log(`${msg} at ${Date.now()}`),
1000,
{ mode: "throttle" } // 或 'debounce'
);
// 切换模式
limiter.mode = "debounce";