Open shfshanyue opened 3 years ago
一个简单的订阅发布模式实现如下,主要有两个核心 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 })
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
})
}
}
}
之前去B站面试,还追问了循环订阅如何处理。
@infjer 何为循环订阅
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);
}
}
}
// 实现发布订阅模式
// 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'})
使用 JS 实现一个发布订阅器,
Event
,示例如下:API 如下: