2694. 事件发射器
2694. 事件发射器
题目
Design an EventEmitter
class. This interface is similar (but with some differences) to the one found in Node.js or the Event Target interface of the DOM. The EventEmitter
should allow for subscribing to events and emitting them.
Your EventEmitter
class should have the following two methods:
subscribe: This method takes in two arguments: the name of an event as a string and a callback function. This callback function will later be called when the event is emitted.
An event should be able to have multiple listeners for the same event.
When emitting an event with multiple callbacks, each should be called in the order in which they were subscribed. An array of results should be returned. You can assume no callbacks passed to
subscribe
are referentially identical.The
subscribe
method should also return an object with anunsubscribe
method that enables the user to unsubscribe. When it is called, the callback should be removed from the list of subscriptions andundefined
should be returned.emit: This method takes in two arguments: the name of an event as a string and an optional array of arguments that will be passed to the callback(s). If there are no callbacks subscribed to the given event, return an empty array. Otherwise, return an array of the results of all callback calls in the order they were subscribed.
Example 1:
Input:
actions = ["EventEmitter", "emit", "subscribe", "subscribe", "emit"],
values = [[], ["firstEvent"], ["firstEvent", "function cb1() { return 5; }"], ["firstEvent", "function cb1() { return 6; }"], ["firstEvent"]]
Output: [[],["emitted",[]],["subscribed"],["subscribed"],["emitted",[5,6]]]
Explanation:
const emitter = new EventEmitter();
emitter.emit("firstEvent"); // [], no callback are subscribed yet
emitter.subscribe("firstEvent", function cb1() { return 5; });
emitter.subscribe("firstEvent", function cb2() { return 6; });
emitter.emit("firstEvent"); // [5, 6], returns the output of cb1 and cb2
Example 2:
Input:
actions = ["EventEmitter", "subscribe", "emit", "emit"],
values = [[], ["firstEvent", "function cb1(...args) { return args.join(','); }"], ["firstEvent", [1,2,3]], ["firstEvent", [3,4,6]]]
Output: [[],["subscribed"],["emitted",["1,2,3"]],["emitted",["3,4,6"]]]
Explanation: Note that the emit method should be able to accept an OPTIONAL array of arguments.
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"]
Example 3:
Input:
actions = ["EventEmitter", "subscribe", "emit", "unsubscribe", "emit"],
values = [[], ["firstEvent", "(...args) => args.join(',')"], ["firstEvent", [1,2,3]], [0], ["firstEvent", [4,5,6]]]
Output: [[],["subscribed"],["emitted",["1,2,3"]],["unsubscribed",0],["emitted",[]]]
Explanation:
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]); // [], there are no subscriptions
Example 4:
Input:
actions = ["EventEmitter", "subscribe", "subscribe", "unsubscribe", "emit"],
values = [[], ["firstEvent", "x => x + 1"], ["firstEvent", "x => x + 2"], [0], ["firstEvent", [5]]]
Output: [[],["subscribed"],["emitted",["1,2,3"]],["unsubscribed",0],["emitted",[7]]]
Explanation:
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]
Constraints:
1 <= actions.length <= 10
values.length === actions.length
- All test cases are valid, e.g. you don't need to handle scenarios when unsubscribing from a non-existing subscription.
- There are only 4 different actions:
EventEmitter
,emit
,subscribe
, andunsubscribe
. - The
EventEmitter
action doesn't take any arguments. - The
emit
action takes between either 1 or 2 arguments. The first argument is the name of the event we want to emit, and the 2nd argument is passed to the callback functions. - The
subscribe
action takes 2 arguments, where the first one is the event name and the second is the callback function. - The
unsubscribe
action takes one argument, which is the 0-indexed order of the subscription made before.
题目大意
设计一个 EventEmitter
类。这个接口与 Node.js 或 DOM 的 Event Target 接口相似,但有一些差异。EventEmitter
应该允许订阅事件和触发事件。
你的 EventEmitter
类应该有以下两个方法:
subscribe: 这个方法接收两个参数: 一个作为字符串的事件名和一个回调函数。当事件被触发时,这个回调函数将被调用。
一个事件应该能够有多个监听器。当触发带有多个回调函数的事件时,应按照订阅的顺序依次调用每个回调函数。应返回一个结果数组。你可以假设传递给
subscribe
的回调函数都不是引用相同的。subscribe
方法还应返回一个对象,其中包含一个unsubscribe
方法,使用户可以取消订阅。当调用unsubscribe
方法时,回调函数应该从订阅列表中删除,并返回 undefined。emit: 这个方法接收两个参数:一个作为字符串的事件名和一个可选的参数数组,这些参数将传递给回调函数。如果没有订阅给定事件的回调函数,则返回一个空数组。否则,按照它们被订阅的顺序返回所有回调函数调用的结果数组。
提示:
1 <= actions.length <= 10
values.length === actions.length
- 所有测试用例都是有效的。例如,你不需要处理取消一个不存在的订阅的情况。
- 只有 4 种不同的操作:
EventEmitter
、emit
、subscribe
和unsubscribe
。EventEmitter
操作没有参数。 emit
操作接收 1 或 2 个参数。第一个参数是要触发的事件名,第二个参数传递给回调函数。subscribe
操作接收 2 个参数,第一个是事件名,第二个是回调函数。unsubscribe
操作接收一个参数,即之前进行订阅的顺序(从 0 开始)。
解题思路
数据结构选择:
- 使用
Map
对象来存储事件名称和对应的回调函数数组。Map
提供了高效的键值对存储,便于根据事件名称快速查找和更新回调函数。
- 使用
订阅事件:
- 实现
subscribe(eventName, callback)
方法,用于注册事件和回调函数。 - 检查事件是否已经存在,如果不存在,则初始化为一个空数组。然后将回调函数添加到事件的回调数组中,并更新
Map
。 - 返回一个包含
unsubscribe
方法的对象,以便在需要时注销事件。
- 实现
注销事件:
- 在
unsubscribe
方法中,从事件的回调数组中移除指定的回调函数。使用filter
方法来创建一个新的回调数组,并更新Map
中的事件。
- 在
触发事件:
- 实现
emit(eventName, args = [])
方法,用于触发事件。 - 获取对应事件的回调函数数组,并调用每个回调函数,传递参数
args
。使用map
方法返回所有回调函数的返回值数组。
- 实现
复杂度分析
- 时间复杂度:每个方法的时间复杂度是
O(m)
,其中m
是该事件的回调函数数量 - 空间复杂度:
O(n)
,其中n
是事件的数量和每个事件的回调函数数量,在events
中存储了所有事件及其回调。
subscribe()
方法:- 时间复杂度:
O(1)
(平均情况),添加回调函数到数组的操作是常数时间,但在最坏情况下,如果回调数组很长,可能需要O(m)
(m
是该事件的回调数量)。 - 空间复杂度:
O(1)
(不考虑回调函数的存储),每次调用只使用常量空间来存储状态。
- 时间复杂度:
unsubscribe()
方法:- 时间复杂度:
O(m)
,其中m
是该事件的回调函数数量,需要遍历回调数组来找到并移除指定的回调函数。 - 空间复杂度:
O(1)
,不需要额外空间,只是修改现有的回调数组。
- 时间复杂度:
emit()
方法:- 时间复杂度:
O(m)
,其中m
是该事件的回调函数数量,需要遍历所有注册的回调函数并执行它们。 - 空间复杂度:
O(1)
,只是在方法内部传递参数,不需要额外存储。
- 时间复杂度:
代码
class EventEmitter {
constructor() {
this.events = new Map();
}
/**
* @param {string} eventName
* @param {Function} callback
* @return {Object}
*/
subscribe(eventName, callback) {
const callbacks = this.events.get(eventName) || [];
// 添加回调函数
callbacks.push(callback);
this.events.set(eventName, callbacks);
return {
unsubscribe: () => {
// 移除回调函数
this.events.set(
eventName,
this.events.get(eventName).filter((i) => i !== callback)
);
return undefined;
}
};
}
/**
* @param {string} eventName
* @param {Array} args
* @return {Array}
*/
emit(eventName, args = []) {
const callbacks = this.events.get(eventName) || [];
// 调用每个回调函数,传递参数
return callbacks.map((cb) => cb(...args));
}
}
/**
* const emitter = new EventEmitter();
*
* // Subscribe to the onClick event with onClickCallback
* function onClickCallback() { return 99 }
* const sub = emitter.subscribe('onClick', onClickCallback);
*
* emitter.emit('onClick'); // [99]
* sub.unsubscribe(); // undefined
* emitter.emit('onClick'); // []
*/