防抖(Debounce)
防抖:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
应用场景
- 搜索框输入(等待用户输入完成后再搜索)
- 窗口大小调整(调整完成后计算布局)
- 表单验证(输入完成后验证)
基础版本
javascript
function debounce(func, wait) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
立即执行版本(先执行一次,再防抖)
javascript
function debounce(func, wait, imediate) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (timeout) clearTimeout(timeout);
if (imediate) {
// 如果已经执行过了,不再执行
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) {
func.apply(context, args);
}
} else {
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
}
};
}
带返回值版本(需要Promise)
javascript
function debounce(func, wait, immediate) {
let timeout, result;
const debounced = function () {
const context = this;
const args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) {
result = func.apply(context, args);
}
} else {
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
}
return result;
};
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
ES6版本
javascript
class Debouncer {
constructor(func, wait, immediate = false) {
this.func = func;
this.wait = wait;
this.immediate = immediate;
this.timeout = null;
this.result = null;
}
execute(...args) {
const context = this;
if (this.timeout) clearTimeout(this.timeout);
if (this.immediate) {
const callNow = !this.timeout;
this.timeout = setTimeout(() => {
this.timeout = null;
}, this.wait);
if (callNow) {
this.result = this.func.apply(context, args);
}
} else {
this.timeout = setTimeout(() => {
this.func.apply(context, args);
}, this.wait);
}
return this.result;
}
cancel() {
clearTimeout(this.timeout);
this.timeout = null;
}
flush() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
return this.result;
}
}
}
const debouncer = new Debouncer((msg) => {
console.log("防抖执行:", msg, Date.now());
}, 1000);
// 快速调用
debouncer.execute("队列消息1");
debouncer.execute("队列消息2");
debouncer.execute("队列消息3"); // 只有这个会执行
更通用的类实现(同时支持防抖和节流)
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";