自定义Emitter类once方法实现求助:需返回订阅数并修复执行/移除异常
解决自定义Emitter类once方法的问题
我来帮你搞定这个once方法的问题!首先得明确咱们的核心需求:绑定的回调只会触发一次,触发后自动从订阅列表中移除,同时这个方法要返回指定事件当前的订阅者数量。你之前的问题大概率是没正确包装回调,导致触发时没法自动解绑,或者计数逻辑没处理好。
完整修正后的Emitter类代码
class Emitter { constructor() { // 用对象存储事件:键为事件名,值为回调函数数组 this.events = {}; } on(eventName, callBack) { // 校验事件名类型 if (typeof eventName !== 'string') { throw new TypeError('事件名称必须是字符串类型'); } // 初始化事件数组(如果不存在的话) if (!this.events[eventName]) { this.events[eventName] = []; } // 添加回调到订阅列表 this.events[eventName].push(callBack); } // 修复后的once方法 once(eventName, callBack) { // 先做基础类型校验 if (typeof eventName !== 'string') { throw new TypeError('事件名称必须是字符串类型'); } if (typeof callBack !== 'function') { throw new TypeError('回调参数必须是函数类型'); } // 包装一个一次性回调:执行原回调后自动移除自己 const onceCallback = (...args) => { // 先执行用户传入的原回调 callBack.apply(this, args); // 执行完毕后,立刻从事件列表中移除这个包装后的回调 this.off(eventName, onceCallback); }; // 初始化事件数组(如果不存在的话) if (!this.events[eventName]) { this.events[eventName] = []; } // 将包装后的回调加入订阅列表 this.events[eventName].push(onceCallback); // 返回当前该事件的订阅者总数 return this.events[eventName].length; } // 补充off方法(用于移除回调,之前如果没实现的话必须加上) off(eventName, callBack) { if (typeof eventName !== 'string') { throw new TypeError('事件名称必须是字符串类型'); } // 如果事件不存在,直接返回 if (!this.events[eventName]) return; // 过滤掉要移除的回调 this.events[eventName] = this.events[eventName].filter(cb => cb !== callBack); // 若订阅列表为空,删除该事件键以节省内存 if (this.events[eventName].length === 0) { delete this.events[eventName]; } } // 补充emit方法(触发事件,确保能正确执行回调) emit(eventName, ...args) { if (typeof eventName !== 'string') { throw new TypeError('事件名称必须是字符串类型'); } // 如果事件不存在,直接返回 if (!this.events[eventName]) return; // 复制回调数组再遍历,避免执行一次性回调时修改原数组导致遍历异常 [...this.events[eventName]].forEach(callBack => { callBack.apply(this, args); }); } }
关键逻辑拆解
- 包装一次性回调:不能直接把用户传入的回调加入订阅列表,必须用一个新函数包装它。这个包装函数会先执行原回调,然后立刻调用
off把自己从列表中移除,完美实现“只触发一次”的需求。 - 类型校验:和
on方法保持一致的校验逻辑,避免非法输入导致的bug。 - 返回订阅数:添加完包装后的回调后,直接返回当前事件列表的长度,满足你要求的返回值需求。
- emit时的数组复制:用
[...this.events[eventName]]复制一份回调数组再遍历,防止执行一次性回调时,因off操作修改原数组而导致的遍历遗漏或错误。
测试示例
// 实例化Emitter const emitter = new Emitter(); // 绑定once事件,返回当前订阅数1 const subscriberCount = emitter.once('testEvent', () => console.log('我只会被触发一次!')); console.log(subscriberCount); // 输出:1 // 第一次触发事件,会打印内容,之后回调被自动移除 emitter.emit('testEvent'); // 输出:"我只会被触发一次!" // 第二次触发事件,不会有任何输出(回调已被移除) emitter.emit('testEvent'); // 查看当前testEvent的订阅状态,应该是undefined(因为列表为空被删除了) console.log(emitter.events.testEvent); // 输出:undefined
内容的提问来源于stack exchange,提问作者Sahin Erbay
相关产品推荐
相关产品推荐

