可实现支持命名空间和自动解绑的eventbus:通过嵌套对象树管理事件,按点号分隔路径(如"user.login"),支持通配符回溯匹配;on()返回token用于精准off(),autooff()将token存入weakmap关联宿主生命周期,cleanup()统一清理。

可以实现一个支持命名空间和自动解绑的 EventBus,核心是用嵌套对象管理事件监听器,并结合 WeakMap 或 token 机制追踪绑定关系,让 off() 或组件销毁时能精准清理。
支持命名空间的事件注册与触发
命名空间用点号分隔(如 "user.login"、"ui.modal.close"),内部按层级构建树状结构,便于批量匹配(如通配符 "user.*")或精确查找。
- 注册时将事件名拆解为路径数组,逐层创建子对象,叶子节点存监听器列表
- 触发时先精确匹配完整路径;若未命中,再向上回溯父级(如触发
"user.login"也通知"user.*"和"*") - 支持通配符
*(仅在末尾或单独使用),不支持中间模糊匹配(避免性能陷阱)
自动解绑:基于绑定 Token 或 WeakMap 关联生命周期
手动调用 off() 易遗漏,更可靠的方式是让监听器“自带身份”,并在宿主销毁时统一回收。
- 推荐方式:每次
on()返回唯一 token(如 Symbol 或短 ID),off(token)可精准移除;同时提供autoOff(host, eventName, handler)接口,把 token 存入 host 的私有 WeakMap 中 - 组件场景下(如 Vue/React),在
onMounted或useEffect中调用bus.on(...).autoOff(this),卸载时自动遍历 WeakMap 清理所有该 host 绑定的事件 - 避免用闭包引用 this 导致内存泄漏 —— token 是轻量标识,不持有上下文
事件监听器管理细节
每个事件路径下的监听器需支持优先级、一次性执行、上下文绑定等常见能力,但保持接口简洁。
- 监听器存储为对象数组:
{ fn, ctx, once, priority, token },按priority降序排序(高优先执行) -
once: true的监听器在首次触发后自动从列表中删除(非惰性清理,防止重复触发) -
emit(eventName, ...args)支持异步队列(可选),默认同步执行;若需保证顺序或防重入,可加锁或微任务调度
简单可用的实现骨架(TypeScript + 注释)
以下是一个最小可行核心(无第三方依赖):
class EventBus {
private tree = Object.create(null); // { 'user': { 'login': [...] } }
private tokens = new WeakMap<object symbol>(); // host → [token1, token2]
<p>on(name: string, fn: Function, ctx?: any, once = false, priority = 0): { off: () => void; token: symbol } {
const token = Symbol();
const parts = name.split('.');
let node = this.tree;</p>
<pre class="brush:php;toolbar:false;">for (const part of parts) {
if (!node[part]) node[part] = Object.create(null);
node = node[part];
}
if (!Array.isArray(node._)) node._ = [];
node._.push({ fn, ctx, once, priority, token });
// 按 priority 排序(插入时排序比每次 emit 时排更高效)
node._.sort((a, b) => b.priority - a.priority);
const off = () => this.offByToken(token);
return { off, token };
}
offByToken(token: symbol) { const walk = (obj: any) => { if (obj.) obj. = obj..filter(h => h.token !== token); for (const key in obj) if (key !== '') walk(obj[key]); }; walk(this.tree); }
autoOff(host: object, token: symbol) { let list = this.tokens.get(host); if (!list) { list = []; this.tokens.set(host, list); } list.push(token); }
emit(name: string, ...args: any[]) { const parts = name.split('.'); const matches: Function[] = [];
// 精确匹配 + 通配回溯:user.login → user.login → user.* → *
for (let i = parts.length; i >= 0; i--) {
const path = parts.slice(0, i).concat(i === parts.length ? [] : ['*']);
let node = this.tree;
let valid = true;
for (const p of path) {
if (!node || !node[p]) { valid = false; break; }
node = node[p];
}
if (valid && node._) matches.push(...node._.map(h => h.fn.bind(h.ctx)));
}
for (const fn of matches) fn(...args);
}
// 组件卸载时调用 cleanup(host: object) { const tokens = this.tokens.get(host); if (tokens) { tokens.forEach(t => this.offByToken(t)); this.tokens.delete(host); } } }











