实现一个 JS delegate 事件委托
DebugMi 发布于 2021-11-14 00:39编辑于 2024-08-29 03:45阅读:
今天写了一个平台的 SDK,SDK 不复杂,优先级还很高,必须得在 head 无 defer 地插入,所以体积需要尽可能地小,以免影响页面加载性能
不依赖任何库写前端,都逃不过一个问题:「事件委托」,也叫「事件代理」,依赖场景有:
- 避免循环定义事件导致的性能问题
- 异步插入的 DOM 无法定义事件(DOM 上直接写 onxxx 除外)
先设计 API
delegate("#haha", "click", (e) => console.log(e));
简单实现一下
先定义一个 map,key 为事件类型,value 为绑定的元素数组。因为同一个事件只需要在 document 上监听一次,避免浪费
type DelegateEventMap = Record<
keyof DocumentEventMap,
Array<{
selector: string;
handler: (e: Event) => any;
}>
>;
const delegateEventMap = {} as DelegateEventMap;
/**
* 委托事件
* @param selector 要委托的元素选择器
* @param eventType 事件类型
* @param handler 事件回调
* @example delegate('#btn', 'click', (e) => console.log(e))
*/
function delegate(
selector: string,
eventType: keyof DocumentEventMap,
handler: (e: Event) => any
) {
if (!delegateEventMap.hasOwnProperty(eventType)) {
document.addEventListener(eventType, handleDocument);
delegateEventMap[eventType] = [{ selector, handler }];
}
}
document 的事件回调,当获取到事件的 target 元素之后,如果不匹配,就一层层往它的父级寻找,因为可能点击的是他的父级,然后每次寻找都在 map 里看看当前元素对应,有就执行回调
// 向上寻找节点,直到找到或者到达顶部
function handleDocument(e: Event | null, dom?: HTMLElement) {
if (!e && !dom) {
return;
}
if (dom && dom.tagName === "HTML") {
return;
}
const eventType = e.type;
const selectorInfos = delegateEventMap[eventType] as
| DelegateEventMap["click"]
| undefined;
if (!selectorInfos) {
return;
}
const currentDom = dom || (e?.target as HTMLElement);
const selectorInfo = selectorInfos.find((item) =>
currentDom.matches?.(item.selector)
);
if (selectorInfo) {
selectorInfo.handler(e);
return;
}
const parentDom = currentDom.parentNode as HTMLElement;
if (parentDom) {
handleDocument(e, parentDom);
}
}
以上,代码很少,大体实现了 jQuery 的 delegate 方法
0