有个历史遗留的老项目规范不够好,事件触发按钮较多且未作局部loading和防抖处理,当前任务是想给全局按钮自动添加防抖,用以优化用户体验和服务负载。
几种方案
- 组件维度进行防抖设计,给当前交互对应的事件包裹防抖
- 业务或者整个子应用可以进行自定义指令实现
- 给当前使用的UI组件,二次封装使其拥有防抖功能
- 对于我们这个,考虑全局劫持方案最优
- ✅ 零侵入性:不需要修改任何现有代码
- ✅ 全局覆盖:自动应用于所有按钮,包括动态生成的
- ✅ 维护性好:集中管理,一处修改全局生效
- ✅ 框架无关:纯JavaScript实现,不依赖特定框架
一、EventTarget 是一个 JavaScript 接口,表示可以接收事件的对象。
EventTarget 接口定义了三个主要的方法:
addEventListener():用于绑定事件监听器。removeEventListener():用于移除已绑定的事件监听器。dispatchEvent():用于分发事件,即触发事件。

二、识别目前元素,我们这个项目仅限制button类型
- 验证当前是否为有效DOM元素
element instanceof HTMLElement - 识别几种常用的按钮
javascript
function isButton(element) {
return element instanceof HTMLElement && (
element.tagName === 'BUTTON'
|| (element.tagName === 'INPUT' && ['button', 'submit'].includes(element.type))
|| element.getAttribute('role') === 'button'
);
}
三、防抖
javascript
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
四、核心劫持逻辑
javascript
EventTarget.prototype.addEventListener = function (type, listener, options) {
if (type === 'click' && isButton(this)) {
// 避免重复创建防抖函数
if (debounceMap.has(this)) {
const buttonMap = debounceMap.get(this);
if (buttonMap.has(listener)) {
const existingDebounced = buttonMap.get(listener);
return originalAddEventListener.call(this, type, existingDebounced, options);
}
}
const debouncedListener = debounce(listener, delay);
if (!debounceMap.has(this)) {
debounceMap.set(this, new Map());
}
debounceMap.get(this).set(listener, debouncedListener);
// 注册防抖后的监听器
return originalAddEventListener.call(this, type, debouncedListener, options);
}
//非目标事件保持原样
return originalAddEventListener.call(this, type, listener, options);
};
五、完整示例及使用
- 在项目入口文件
main.js中进行使用即可 import { debounceClick } from './debounce-click.js';
JavaScript
// 保存原始方法的全局变量
let originalAddEventListener;
let originalRemoveEventListener;
export function debounceClick(delay = 300) {
if (window._eventTargetHijackEnabled) {
console.warn('重复初始化');
return;
}
// 备份原始方法
if (!window._originalAddEventListener) {
window._originalAddEventListener = EventTarget.prototype.addEventListener;
window._originalRemoveEventListener = EventTarget.prototype.removeEventListener;
}
originalAddEventListener = window._originalAddEventListener;
originalRemoveEventListener = window._originalRemoveEventListener;
const debounceMap = new WeakMap();
function isButton(element) {
return (
element instanceof HTMLElement &&
(element.tagName === 'BUTTON' ||
(element.tagName === 'INPUT' && ['button', 'submit'].includes(element.type)) ||
element.getAttribute('role') === 'button')
);
}
// 防抖函数
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 劫持addEventListener
EventTarget.prototype.addEventListener = function (type, listener, options) {
if (type === 'click' && isButton(this)) {
// 避免重复创建防抖函数
if (debounceMap.has(this)) {
const buttonMap = debounceMap.get(this);
if (buttonMap.has(listener)) {
const existingDebounced = buttonMap.get(listener);
return originalAddEventListener.call(this, type, existingDebounced, options);
}
}
const debouncedListener = debounce(listener, delay);
if (!debounceMap.has(this)) {
debounceMap.set(this, new Map());
}
debounceMap.get(this).set(listener, debouncedListener);
return originalAddEventListener.call(this, type, debouncedListener, options);
}
return originalAddEventListener.call(this, type, listener, options);
};
// 劫持removeEventListener,不然会导致无法移除劫持处理后的事件监听器
EventTarget.prototype.removeEventListener = function (type, listener, options) {
if (type === 'click' && isButton(this) && debounceMap.has(this)) {
const buttonMap = debounceMap.get(this);
if (buttonMap.has(listener)) {
const debouncedListener = buttonMap.get(listener);
buttonMap.delete(listener);
if (buttonMap.size === 0) {
debounceMap.delete(this);
}
return originalRemoveEventListener.call(this, type, debouncedListener, options);
}
}
return originalRemoveEventListener.call(this, type, listener, options);
};
window._eventTargetHijackEnabled = true;
console.log(`劫持防抖启用(delay: ${delay}ms)`);
}
// 回滚原始
export function disableEventTargetHijack() {
if (!window._eventTargetHijackEnabled) {
console.warn('EventTarget劫持未启用,无需禁用');
return;
}
// 恢复原始方法
if (window._originalAddEventListener) {
EventTarget.prototype.addEventListener = window._originalAddEventListener;
}
if (window._originalRemoveEventListener) {
EventTarget.prototype.removeEventListener = window._originalRemoveEventListener;
}
// 清理所有无用的全局变量
delete window._originalAddEventListener;
delete window._originalRemoveEventListener;
delete window._eventTargetHijackEnabled;
console.warn('EventTarget劫持禁用完成');
}