事件委托利用事件冒泡机制,将监听器绑定在父元素上,通过event.target和closest()匹配子元素;支持动态元素、多事件类型、选择器过滤、参数透传及解绑功能。

<p>事件委托的核心是利用事件冒泡,把事件监听器绑定在父元素上,通过 <strong>event.target</strong> 判断真正触发事件的子元素是否匹配选择器。原生实现一个类似 jQuery <code>.on()</code> 的代理方法,关键在于:支持动态添加的元素、支持多种事件类型、支持选择器过滤、支持事件参数透传和事件解绑。</p>
<h3>基础版:单次绑定 + 冒泡判断</h3>
<p>最简实现,适用于静态结构或简单场景:</p>
<font color="#666">
<pre class="brush:php;toolbar:false;"><code>function delegate(parent, selector, eventType, handler) {
parent.addEventListener(eventType, function(e) {
const target = e.target;
// 从 target 向上查找匹配 selector 的元素(含自身)
let el = target;
while (el && el !== parent) {
if (el.matches(selector)) {
handler.call(el, e);
return;
}
el = el.parentElement;
}
});
}</code>
用法示例:
<code>const ul = document.getElementById('list');
delegate(ul, 'li', 'click', function(e) {
console.log('点击了 li:', this.textContent);
});</code>
增强版:支持事件解绑与多事件
为支持多次调用、解绑、多个事件类型,需维护监听器映射关系,并封装 off() 方法:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
<code>// 存储所有委托关系:{ parent: { [eventType]: [{ selector, handler, fn }] } }
const delegateStore = new WeakMap();
function on(parent, selector, eventType, handler) {
if (!delegateStore.has(parent)) {
delegateStore.set(parent, {});
}
const eventMap = delegateStore.get(parent);
if (!eventMap[eventType]) {
eventMap[eventType] = [];
// 实际绑定原生事件(只绑一次)
parent.addEventListener(eventType, function(e) {
const matches = eventMap[eventType].filter(item =>
e.target.matches(item.selector) ||
e.target.closest(item.selector)
);
for (const match of matches) {
match.fn.call(e.target.closest(match.selector), e);
}
});
}
// 存储委托项,fn 是包装后的处理函数,用于 later off()
const fn = function(e) {
handler.call(this, e);
};
eventMap[eventType].push({ selector, handler, fn });
return {
off() {
const list = eventMap[eventType];
const index = list.findIndex(item => item.handler === handler && item.selector === selector);
if (index > -1) list.splice(index, 1);
// 可选:若该 eventType 下无剩余委托,可 removeEventListener(但需额外保存 listener 引用)
}
};
}</code>
使用方式:
const unbind = on(document.body, '.btn', 'click', handleClick);- 后续可调用
unbind.off()移除该条委托 - 支持同一元素多个事件:
on(el, 'a', 'mouseover', ...)和on(el, 'a', 'click', ...)
进阶细节:处理事件捕获、自定义事件、委托到 document
实际项目中要注意几个易错点:
-
closest() 比 matches() 更健壮:因为
e.target可能是子元素(如<span>在<li>内),用e.target.closest(selector)直接获取最近匹配祖先更可靠 -
避免重复绑定相同事件:可在
addEventListener前检查是否已存在对应监听器(借助 WeakMap 标记) -
支持捕获阶段:增加第 5 个参数
useCapture,传给addEventListener -
兼容 document 动态元素:用
document.addEventListener时,注意parent为document,此时e.target.closest仍有效
轻量工具函数推荐写法(生产可用)
综合以上,一个简洁、可复用、无依赖的版本:
<code>const delegate = (function() {
const store = new WeakMap();
return function(parent, selector, type, handler, useCapture = false) {
if (!store.has(parent)) store.set(parent, new Map());
const map = store.get(parent);
const key = `${type}|${useCapture}`;
if (!map.has(key)) {
map.set(key, []);
parent.addEventListener(type, function(e) {
const el = e.target.closest(selector);
if (el) {
handler.call(el, e);
}
}, useCapture);
}
map.get(key).push(handler);
return {
destroy() {
const list = map.get(key);
const i = list.indexOf(handler);
if (i > -1) list.splice(i, 1);
if (list.length === 0) {
map.delete(key);
parent.removeEventListener(type, arguments.callee, useCapture);
}
}
};
};
})();</code>
这个版本足够轻量,支持解绑,自动清理空监听器,且不污染全局命名空间。它不模拟 jQuery 全部 API(如事件命名空间、trigger),但覆盖了 95% 的委托需求。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










