高级农民
- 积分
- 4142
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2017-1-22
- 最后登录
- 1970-1-1
|
2694. Event Emitter
Medium
conpanies icon
Companies
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 an unsubscribe method that enables the user to unsubscribe. When it is called, the callback should be removed from the list of subscriptions and undefined 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"],["subscribed"],["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, and unsubscribe.
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 的 EventEmitter,有两个方法:
subscribe(eventName, callback):订阅事件。
同一个事件可以有多个回调,触发时按订阅顺序依次执行。
返回一个对象,里面有 unsubscribe 方法。调用它会把这个回调从订阅列表里删掉,并返回 undefined。
emit(eventName, args = []):触发事件。
args 是可选的参数数组,会展开后传给每个回调。
如果该事件没有任何订阅者,返回 []。
否则返回所有回调返回值组成的数组,顺序与订阅顺序一致。
题目还说明不会出现两个引用完全相同的回调,所以可以直接用回调函数本身来定位要删除的订阅。示例里的 [0]、"subscribed" 等只是测试框架的输出格式,不用自己实现。
解题思路
用一个 Map<string, Callback[]> 保存数据:key 是事件名,value 是该事件的回调数组,数组顺序就是订阅顺序。
subscribe:如果这个事件还没有数组就先创建,然后把回调 push 进去。返回的 unsubscribe 通过闭包记住 eventName 和 callback,调用时用 indexOf 找到位置,再 splice 删除。
emit:取出该事件的回调数组(没有就用空数组),用 map 依次执行 cb(...args),map 的结果就是返回值数组。事件不存在时,空数组的 map 结果正好是 []。- type Callback = (...args: any[]) => any;
- type Subscription = {
- unsubscribe: () => void
- }
- class EventEmitter {
- // 事件名 -> 回调数组(按订阅顺序存放)
- private events = new Map<string, Callback[]>();
- subscribe(eventName: string, callback: Callback): Subscription {
- if (!this.events.has(eventName)) {
- this.events.set(eventName, []);
- }
- this.events.get(eventName)!.push(callback);
- return {
- unsubscribe: () => {
- const list = this.events.get(eventName)!;
- const idx = list.indexOf(callback); // 回调互不相同,可直接定位
- if (idx !== -1) list.splice(idx, 1);
- }
- };
- }
- emit(eventName: string, args: any[] = []): any[] {
- const list = this.events.get(eventName) ?? [];
- return list.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'); // []
- */
复制代码 |
|