EventEmitter是Node.js的内置模块events提供的一个类,它是Node事件流的核心。
下面模拟实现一个Node中的EventEmitter。具体包括以下方法:
方法名 | 方法描述 |
---|---|
addListener(event, listener) | 为指定事件添加一个监听器到监听器数组的尾部。 |
prependListener(event,listener) | 与addListener相对,为指定事件添加一个监听器到监听器数组的头部。 |
on(event, listener) | 其实就是addListener的别名 |
once(event, listener) | 为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。 |
prependOnceListener(event, listener) | 添加单次监听器 listener 到名为 eventName 的事件的监听器数组的开头。 |
removeListener(event, listener) | 移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器 |
off(event, listener) | removeListener的别名 |
removeAllListeners([event]) | 移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。 |
setMaxListeners(n) | 默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于提高监听器的默认限制的数量。 |
listeners(event) | 返回指定事件的监听器数组。 |
emit(event[, ...rest]) | 按参数的顺序执行每个监听器,如果事件有注册监听返回 true,否则返回 false。 |
以下是第一个版本,维护events和onceEvents的目的是,通过once订阅的事件,监听器在内部封装了一层,所以移除时传入的监听器不是实际订阅的监听器。一开始没想到好的方法,所以维护了两个对象。可以下翻直接看第二版。
class EventEmitter {
constructor() {
this._events = Object.create(null)
this._onceEvents = Object.create(null)
this._maxListeners = 10
}
_perform(events, rest) {
events.forEach(fn => fn.apply(this, rest))
const { _maxListeners } = this
if (!(_maxListeners !== 0 || _maxListeners !== Infinity)) {
console.log(55765)
if (events.length > _maxListeners) {
console.error(
'MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit'
)
}
}
return true
}
emit(event, ...rest) {
const events = this._events[event]
const onceEvents = this._onceEvents[event]
if (events) {
return this._perform(events, rest)
}
if (onceEvents) {
delete this._onceEvents[event]
return this._perform(onceEvents, rest)
}
return false
}
_bind(type, event, listener, method = 'push') {
const events = this[type][event]
this[type][event]?.[method](listener) || (this[type][event] = [listener])
return this
}
addListener(event, listener) {
return this._bind('_events', event, listener)
}
prependListener(event, listener) {
return this._bind('_events', event, listener, 'unshift')
}
on(event, listener) {
return this.addListener(event, listener)
}
off(event, listener) {
return this.removeListener(event, listener)
}
prependOnceListener(event, listener) {
return this._bind('_onceEvents', event, fn, 'unshift')
}
once(event, listener) {
return this._bind('_onceEvents', event, listener)
}
_remove(type, event, listener) {
const events = this[type][event]
if (events) {
this[type][event] = events.filter(fn => fn !== listener)
}
return this
}
removeListener(event, listener) {
let type = '_events'
if (this._onceEvents[event]) {
type = '_onceEvents'
}
return this._remove(type, event, listener)
}
removeAllListeners(event) {
if (this._events[event]) {
delete this._events[event]
} else if (this._onceEvents[event]) {
delete this._onceEvents[event]
}
return this
}
listeners(event) {
return this._events[event] || this._onceEvents[event]
}
setMaxListeners(limit) {
this._maxListeners = limit
}
}
以下是最终版,对于once订阅的事件,在内部创建了一个函数,并且将监听器挂载在函数上,在移除时会进一步判断监听器是否有fn属性。这样只需要维护一个events即可。
class EventEmitter {
constructor() {
this._events = Object.create(null)
this._maxListeners = 10
}
_bind(event, listener, method = 'push') {
;(this._events[event] || (this._events[event] = []))[method](listener)
return this
}
addListener(event, listener) {
return this._bind(event, listener)
}
on(event, listener) {
return this.addListener(event, listener)
}
prependListener(event, listener) {
this._bind(event, listener, 'unshift')
}
_once(event, listener, method = 'push') {
const foo = (...rest) => {
this.removeListener(event, foo)
listener.apply(this, rest)
}
foo.fn = listener
return this._bind(event, foo, method)
}
once(event, listener) {
return this._once(event, listener, 'push')
}
prependOnceListener(event, listener) {
return this._once(event, listener, 'unshift')
}
removeListener(event, listener) {
if (!arguments.length) {
this._events = Object.create(null)
return this
}
const cbs = this._events[event]
if (!cbs) return this
if (!listener) {
this._events[event] = null
return this
}
// 这种方式有个缺陷是,假设在多个 on 之间 使用once 监听了一次,那么在off中移除监听器时会导致索引错误
// let cb
// let i = cbs.length
// while (i--) {
// cb = cbs[i]
// if (cb === listener || cb.fn === listener) {
// cbs.splice(i, 1)
// break
// }
// }
// OR
this._events[event] = cbs.filter(cb => {
// if (cb === listener || cb.fn === listener) return false
// return true
// OR
return !(cb === listener || cb.fn === listener)
})
return this
}
off(event, listener) {
return this.removeListener(event, listener)
}
emit(event, ...args) {
const cbs = this._events[event]
if (cbs) {
const len = cbs.length
const { _maxListeners } = this
cbs.forEach(cb => {
cb.apply(this, args)
})
if (!(_maxListeners !== 0 || _maxListeners !== Infinity)) {
if (len > _maxListeners) {
console.error(
'MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit'
)
}
}
}
return this
}
listeners(event) {
return this._events[event]
}
setMaxListeners(limit) {
this._maxListeners = limit
}
}
对于once订阅的监听器,通过将其挂载在封装的函数上,不仅可以同时通过on 和 once订阅同名事件,而且在emit完之后,移除once订阅的监听器,这样就不需要维护两个对象,相比较方式一在emit时还要合并两个事件的监听器去执行,完成之后单独移除once监听的事件,方式二简直完美。
网友评论