如何使用 JS 实现一个发布订阅模式
更多描述 使用 JS 实现一个发布订阅器,
Event
,示例如下:
const e = new Event();
e.on("click", (x) => console.log(x.id));
e.once("click", (x) => console.log(id));
//=> 3
e.emit("click", { id: 3 });
//=> 4
e.emit("click", { id: 4 });
API 如下:
class Event {
emit(type, ...args) {}
on(type, listener) {}
once(type, listener) {}
off(type, listener) {}
}
Issue 欢迎在 Gtihub Issue 中回答此问题: Issue 631
Author 回答者: shfshanyue
一个简单的订阅发布模式实现如下,主要有两个核心 API
emit
: 发布一个事件on
: 监听一个事件off
: 取消一个事件监听
实现该模式,使用一个 events 维护发布的事件:
const events = {
click: [
{
once: true,
listener: callback,
},
{
listener: callback,
},
],
};
具体实现代码如下所示
class Event {
events = {};
emit(type, ...args) {
const listeners = this.events[type];
for (const listener of listeners) {
listener.listener(...args);
if (listener.once) {
this.off(type, listener.listener);
}
}
}
on(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type].push({ listener });
}
once(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type].push({ listener, once: true });
}
off(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type] = this.events[type].filter(
(listener) => listener.listener !== listener,
);
}
}
以上代码不够优雅,且有点小瑕疵,再次实现如下,代码可见 如何实现发布订阅器 - codepen
class Event {
events = {};
emit(type, ...args) {
const listeners = this.events[type];
for (const listener of listeners) {
listener(...args);
}
}
on(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type].push(listener);
}
once(type, listener) {
const callback = (...args) => {
this.off(type, callback);
listener(...args);
};
this.on(type, callback);
}
off(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type] = this.events[type].filter(
(callback) => callback !== listener,
);
}
}
const e = new Event();
const callback = (x) => {
console.log("Click", x.id);
};
e.on("click", callback);
e.on("click", callback);
// 只打印一次
const onceCallback = (x) => console.log("Once Click", x.id);
e.once("click", onceCallback);
e.once("click", onceCallback);
//=> 3
e.emit("click", { id: 3 });
//=> 4
e.emit("click", { id: 4 });
Author 回答者: heretic-G
class Center {
eventMap = {};
on(event, fun) {
this.#add(event, fun, "on");
}
once(event, fun) {
this.#add(event, fun, "once");
}
#add(event, fun, type) {
if (typeof fun !== "function")
throw new TypeError(`${fun} is not a function`);
if (!event) throw new Error(`need type`);
if (!this.eventMap[event]) {
this.eventMap[event] = [];
}
this.eventMap[event].push({
event: fun,
type: type,
});
}
emit(event, ...args) {
if (this.eventMap[event]) {
this.eventMap[event] = this.eventMap[event].filter((curr) => {
curr.data(...args);
return curr.type !== "once";
});
}
}
remove(event, fun) {
if (this.eventMap[event]) {
this.eventMap[event] = this.eventMap[event].filter((curr) => {
return curr.event !== fun;
});
}
}
}
Author 回答者: infjer
之前去B站面试,还追问了循环订阅如何处理。
Author 回答者: shfshanyue
@infjer 何为循环订阅
Author 回答者: leblancy
class Emitter {
constructor() {
this.events = {};
}
emit(type, ...args) {
if (this.events[type] && this.events[type].length > 0) {
this.events[type].forEach(cb => {
cb.apply(this, args);
});
}
}
on(type, cb) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(cb);
}
once(type, cb) {
const func = (...args) => {
this.off(type, func);
cb.apply(this, args);
};
this.on(type, func);
}
off(type, cb) {
if (!cb) {
this.events[type] = null;
} else {
this.events[type].filter(exec => exec !== cb);
}
}
}
Author 回答者: Hazel-Lin
// 实现发布订阅模式
// on 订阅
// emit 发布
// off 取消订阅
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅
on(type, callback) {
this.events[type] = this.events[type] || [];
this.events[type].push(callback);
}
// 取消订阅
off(type, callback) {
if (!this.events[type]) return;
this.events[type] = this.events[type].filter((event) => event !== callback);
}
// 发布
emit(type, ...args) {
if (!this.events[type]) return;
// 遍历事件
this.events[type].forEach((event) => {
event(...args);
});
}
}
const e = new EventEmitter();
const callback2 = (data) => console.log(`${data.name}`);
e.on("click", callback2);
e.emit("click", { name: "xiaoming" });
e.off("click", callback2);
e.emit("click", { name: "lili" });