说在前面
🎈简单实现一个事件触发器。
题目描述
设计一个 EventEmitter
类。这个接口与 Node.js 或 DOM 的 Event Target 接口相似,但有一些差异。EventEmitter
应该允许订阅事件和触发事件。
你的 EventEmitter
类应该有以下两个方法:
- subscribe - 这个方法接收两个参数:一个作为字符串的事件名和一个回调函数。当事件被触发时,这个回调函数将被调用。 一个事件应该能够有多个监听器。当触发带有多个回调函数的事件时,应按照订阅的顺序依次调用每个回调函数。应返回一个结果数组。你可以假设传递给
subscribe
的回调函数都不是引用相同的。subscribe
方法还应返回一个对象,其中包含一个unsubscribe
方法,使用户可以取消订阅。当调用unsubscribe
方法时,回调函数应该从订阅列表中删除,并返回 undefined。 - emit - 这个方法接收两个参数:一个作为字符串的事件名和一个可选的参数数组,这些参数将传递给回调函数。如果没有订阅给定事件的回调函数,则返回一个空数组。否则,按照它们被订阅的顺序返回所有回调函数调用的结果数组。
示例 1:
输入: actions = ["EventEmitter", "emit", "subscribe", "subscribe", "emit"], values = [[], ["firstEvent", "function cb1() { return 5; }"], ["firstEvent", "function cb1() { return 5; }"], ["firstEvent"]] 输出: [[],["emitted",[]],["subscribed"],["subscribed"],["emitted",[5,6]]] 解释: const emitter = new EventEmitter(); emitter.emit("firstEvent"); // [], 还没有订阅任何回调函数 emitter.subscribe("firstEvent", function cb1() { return 5; }); emitter.subscribe("firstEvent", function cb2() { return 6; }); emitter.emit("firstEvent"); // [5, 6], 返回 cb1 和 cb2 的输出
示例 2:
输入: actions = ["EventEmitter", "subscribe", "emit", "emit"], values = [[], ["firstEvent", "function cb1(...args) { return args.join(','); }"], ["firstEvent", [1,2,3]], ["firstEvent", [3,4,6]]] 输出: [[],["subscribed"],["emitted",["1,2,3"]],["emitted",["3,4,6"]]] 解释: 注意 emit 方法应该能够接受一个可选的参数数组。 const emitter = new EventEmitter(); emitter.subscribe("firstEvent, function cb1(...args) { return args.join(','); }); emitter.emit("firstEvent", [1, 2, 3]); // ["1,2,3"] emitter.emit("firstEvent", [3, 4, 6]); // ["3,4,6"]
示例 3:
输入: actions = ["EventEmitter", "subscribe", "emit", "unsubscribe", "emit"], values = [[], ["firstEvent", "(...args) => args.join(',')"], ["firstEvent", [1,2,3]], [0], ["firstEvent", [4,5,6]]] 输出: [[],["subscribed"],["emitted",["1,2,3"]],["unsubscribed",0],["emitted",[]]] 解释: const emitter = new EventEmitter(); const sub = emitter.subscribe("firstEvent", (...args) => args.join(',')); emitter.emit("firstEvent", [1, 2, 3]); // ["1,2,3"] sub.unsubscribe(); // undefined emitter.emit("firstEvent", [4, 5, 6]); // [], 没有订阅者
示例 4:
输入: actions = ["EventEmitter", "subscribe", "subscribe", "unsubscribe", "emit"], values = [[], ["firstEvent", "x => x + 1"], ["firstEvent", "x => x + 2"], [0], ["firstEvent", [5]]] 输出: [[],["subscribed"],["emitted",["1,2,3"]],["unsubscribed",0],["emitted",[7]]] 解释: const emitter = new EventEmitter(); const sub1 = emitter.subscribe("firstEvent", x => x + 1); const sub2 = emitter.subscribe("firstEvent", x => x + 2); sub1.unsubscribe(); // undefined emitter.emit("firstEvent", [5]); // [7]
提示:
1 <= actions.length <= 10
values.length === actions.length
- 所有测试用例都是有效的。例如,你不需要处理取消一个不存在的订阅的情况。
- 只有 4 种不同的操作:
EventEmitter
、emit
、subscribe
和unsubscribe
。EventEmitter
操作没有参数。 emit
操作接收 1 或 2 个参数。第一个参数是要触发的事件名,第二个参数传递给回调函数。subscribe
操作接收 2 个参数,第一个是事件名,第二个是回调函数。unsubscribe
操作接收一个参数,即之前进行订阅的顺序(从 0 开始)。
解题思路
这是一个简单的事件触发器实现,通过subscribe
方法订阅指定事件并注册回调函数,通过emit
方法触发指定事件,并执行所有相关回调函数。此外,subscribe
方法返回一个包含取消订阅函数的对象,可以在需要取消订阅时调用。
具体来说,该事件触发器实现主要有以下几个方法:
- constructor:构造函数,创建一个Map实例来存储事件及其对应的回调函数列表。
- subscribe:订阅指定事件并注册回调函数,返回一个包含取消订阅函数的对象。
- unsubscribe:取消订阅指定事件的回调函数。
- emit:触发指定事件,并执行所有相关回调函数。
下面是这些方法的详细说明:
constructor
constructor(){ this.eventMap = new Map(); }
构造函数,创建一个Map实例来存储事件及其对应的回调函数列表。
subscribe
subscribe(eventName, callback) { const eventList = this.eventMap.get(eventName) || []; const key = Math.ceil(Math.random() * 10000); eventList.push({ key, callback }); this.eventMap.set(eventName,eventList); return { unsubscribe: () => { const eventList = this.eventMap.get(eventName) || []; const index = eventList.findIndex(item=>item.key === key); if(index === -1) return; eventList.splice(index,1); if(eventList.length === 0) this.eventMap.delete(eventName); this.eventMap.set(eventName,eventList); } }; }
订阅指定事件并注册回调函数,返回一个包含取消订阅函数的对象。
该方法首先获取指定事件对应的回调函数列表,如果不存在则新建一个空列表。然后,生成一个随机数作为回调函数的唯一键,并将其与回调函数一起存入回调函数列表中。最后,更新事件对应的回调函数列表,并返回一个包含unsubscribe方法的对象。
unsubscribe
unsubscribe: () => { const eventList = this.eventMap.get(eventName) || []; const index = eventList.findIndex(item=>item.key === key); if(index === -1) return; eventList.splice(index,1); if(eventList.length === 0) this.eventMap.delete(eventName); this.eventMap.set(eventName,eventList); }
取消订阅指定事件的回调函数。
该方法首先获取指定事件对应的回调函数列表,然后在列表中查找指定键的回调函数,并将其从列表中删除。如果删除后回调函数列表为空,则删除该事件对应的键值对。
emit
emit(eventName, args = []) { const eventList = this.eventMap.get(eventName); if(!eventList) return []; const res = []; eventList.forEach(fn=>{ res.push(fn.callback(...args)); }) return res; }
触发指定事件,并执行所有相关回调函数。
该方法首先获取指定事件对应的回调函数列表,如果不存在则返回空数组。然后,对于列表中的每个回调函数,按照顺序执行其对应的函数,并将返回值存入一个数组中。最后,返回保存了所有返回值的数组。
AC代码
class EventEmitter { constructor(){ this.eventMap = new Map(); } /** * @param {string} eventName * @param {Function} callback * @return {Object} */ subscribe(eventName, callback) { const eventList = this.eventMap.get(eventName) || []; const key = Math.ceil(Math.random() * 10000); eventList.push({ key, callback }); this.eventMap.set(eventName,eventList); return { unsubscribe: () => { const eventList = this.eventMap.get(eventName) || []; const index = eventList.findIndex(item=>item.key === key); if(index === -1) return; eventList.splice(index,1); if(eventList.length === 0) this.eventMap.delete(eventName); this.eventMap.set(eventName,eventList); } }; } /** * @param {string} eventName * @param {Array} args * @return {Array} */ emit(eventName, args = []) { const eventList = this.eventMap.get(eventName); if(!eventList) return []; const res = []; eventList.forEach(fn=>{ res.push(fn.callback(...args)); }) return res; } } // const emitter = new EventEmitter(); // function onClickCallback() { return 99 } // const sub = emitter.subscribe('onClick', onClickCallback); // let res = emitter.emit('onClick'); // [99] // sub.unsubscribe(); // undefined // res = emitter.emit('onClick'); // []
公众号
关注公众号『前端也能这么有趣
』,获取更多有趣内容。
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣
』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。