为 Node 和浏览器编写自己的可链接事件发射器类

2025-06-09

为 Node 和浏览器编写自己的可链接事件发射器类

现在是星期六的早上,我还在等咖啡,所以让我们做些小事来为这一天暖和一下吧。

为 Node 和浏览器编写自己的可链接事件发射器类

JavaScript 中的事件发射器通常有三种方法。

添加事件监听器

添加/注册一个侦听器,当事件被分派时该侦听器将被调用。

删除事件监听器

删除/取消注册事件监听器。

调度事件

此方法用于触发某一类型的事件。

班级

首先,我们要确保 Emitter 是实例化的,而不是作为函数调用的。

function Emitter() {
    if (!(this instanceof Emitter)) throw new TypeError('Emitter is not a function.')
    // ...
}

声明一个私有变量来存储监听器。该数组将被填充更多数组,其中 array[0] 是事件类型,array[1] 是回调函数。

/**
 * Store event handlers here.
 * @type {Array}
 * @private
 */
let handlers = []

添加事件监听器

此方法将通过向处理程序添加数组类型的项来为所提供类型的事件添加/注册新的事件监听器,其中array[0] 是类型,array[1] 是回调。

/**
 * Add event listener.
 * @param {string} event type 
 * @param {function} callback function
 */
this.addEventListener = (type, fn) => {
    handlers.push([type, fn])
}

一旦实例化,就可以按如下方式调用 addEventListener:

emitter.addEventListener('message', message => console.log('received a message!', message))

删除事件监听器

我们还需要能够删除不再需要的事件监听器。为此,我们需要删除事件处理程序中所有以 item[0] 为事件类型的项。

/**
 * Remove event listener.
 * @param {string} event type 
 * @param {function} callback function
 */
this.removeEventListener = (type, fn = true) => {
    handlers = handlers.filter(handler => !(handler[0] == type && (fn == true ? true : handler[1] == fn)))
}
emitter.addEventListener('ready', console.log) // console.log will be called when a ready event happens
emitter.removeEventListener('ready', console.log)  // console.log will no longer be called on ready events

调度事件

在浏览器中,触发事件的方法称为dispatchEvent 。在 Node 环境中,它通常称为emit
我们将稍微修改此函数,使其支持通配符事件类型(如https://www.npmjs.com/package/eventemitter2所示)。此外,除了事件数据之外,还会为事件处理程序提供第二个参数类型。当您实现通配符事件类型支持时,此参数有助于确定具体类型。

// without the type argument, this event could be anything
emitter.addEventListener('*', (event, type) => console.log(`an event of type = ${type} was emitted.`))
emitter.addEventListener('user:*', (event, type) => console.log(`something usery happened.`))
/**
 * Dispatch event.
 * @param {string} event type 
 * @param {any} event data
 */
 this.dispatchEvent = (type, data) => {
    handlers.filter(handler => new RegExp("^" + handler[0].split("*").join(".*") + "$").test(type)).forEach(handler => handler[1](data, type))
 }

获取事件监听器

也许您希望能够获取/列出所有事件监听器(某种类型)。

/**
 * Get list of event handlers (of a type) or all if type is not specified
 * @param {string} [event type] (optional) 
 */
 this.getEventListeners = type => {
    if (!type)
        return handlers
    let fns = []
    handlers.filter(handler => handler[0] == type).forEach(handler => fns.push(handler[1]))

    return fns
}

清除事件监听器

我们还添加这个额外的方法,通过重新初始化处理程序来清除所有事件监听器。

/**
 * Clear event listeners
 * @param {string} [event type] (optional)
 */
    this.clearEventListeners = () => { handlers = [] }

迄今为止

我们的 Emitter 类现在看起来像这样。

function Emitter() {
    if (!(this instanceof Emitter)) throw new TypeError('Emitter is not a function.')

    let handlers = []

    this.addEventListener = (type, fn) => {
        handlers.push([type, fn])
    }

    this.removeEventListener = (type, fn = true) => {
        handlers = handlers.filter(handler => !(handler[0] == type && (fn == true ? true : handler[1] == fn)))
    }

    this.dispatchEvent = (type, data) => {
        handlers.filter(handler => new RegExp("^" + handler[0].split("*").join(".*") + "$").test(type)).forEach(handler => handler[1](data, type))
    }

    this.clearEventListeners = () => { handlers = [] }

    this.getEventListeners = type => {
        if (!type)
            return handlers

        let fns = []
        handlers.filter(handler => handler[0] == type).forEach(handler => fns.push(handler[1]))

        return fns
    }
}

恭喜!您已经拥有一个可以运行的事件发射器类。自己尝试一下:

var emitter = new Emitter()
emitter.addEventListener('ready', console.log)
emitter.addEventListener('foo.*', (event, type) => console.log({type,event}))
emitter.dispatchEvent('ready', Date.now())
emitter.dispatchEvent('foo.bar', 'blabalbla')
emitter.removeEventListener('ready', console.log)
emitter.clearEventListeners()

但我们还没完,我承诺过一个可链接的事件发射器。可链接意味着发射器是一个单例,它总是返回自身,允许你不断地调用它的方法。

快捷方式

因为我们不喜欢总是写addEventListenerdispatchEvent ,所以我们添加这些快捷方式。这些快捷方式最后都返回this ,这样就构成了链式调用。

 /**
     * Shortcut for addEventListener.
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.on = (type, fn) => {
        this.addEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for removeEventListener
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.off = (type, fn) => {
        this.removeEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for dispatchEvent
     * @param {string} event type 
     * @param {any} event data
     */
    this.emit = (type, data) => {
        this.dispatchEvent(type, data)
        return this /* chain */
    }

     /**
     * Shortcut for clearEventListeners
     * @param {string} event type 
     */
    this.clear = type => {
        this.clearEventListeners(type)
        return this
    }

    /**
     * 
     * @param {string} [type]
     */
    this.list = type => this.getEventListeners(type)

现在我们的 Event Emitter 类可以像这样访问:

emitter.on('message', message => console.log(message).on('open', onOpen).on('error', console.error).emit('ready', { status: 200, details: 'this is a ready event'})

最终结果:Emitter 类

您的最终 Emitter 类应如下所示:

/**
 * Simpler EventTarget class without the need to dispatch Event instances.
 * @constructor
 * @returns {Emitter} new instance of Emitter
 */
function Emitter() {
    if (!(this instanceof Emitter)) throw new TypeError('Emitter is not a function.')

    /**
     * Store event handlers here.
     * @type {Array}
     * @private
     */
    let handlers = []

    /**
     * Add event listener.
     * @param {string} event type 
     * @param {function} callback function
     */
    this.addEventListener = (type, fn) => {
        handlers.push([type, fn])
    }

    /**
     * Remove event listener.
     * @param {string} event type 
     * @param {function} callback function
     */
    this.removeEventListener = (type, fn = true) => {
        handlers = handlers.filter(handler => !(handler[0] == type && (fn == true ? true : handler[1] == fn)))
    }

    /**
     * Dispatch event.
     * @param {string} event type 
     * @param {any} event data
     */
    this.dispatchEvent = (type, data) => {
        handlers.filter(handler => new RegExp("^" + handler[0].split("*").join(".*") + "$").test(type)).forEach(handler => handler[1](data, type))
    }

    /**
     * Clear event listeners
     * @param {string} [event type] (optional)
     */
    this.clearEventListeners = () => { handlers = [] }

    /**
     * Get list of event handlers (of a type) or all if type is not specified
     * @param {string} [event type] (optional) 
     */
    this.getEventListeners = type => {
        if (!type)
            return handlers

        let fns = []
        handlers.filter(handler => handler[0] == type).forEach(handler => fns.push(handler[1]))

        return fns
    }

    /**
     * Shortcut for addEventListener.
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.on = (type, fn) => {
        this.addEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for removeEventListener
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.off = (type, fn) => {
        this.removeEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for dispatchEvent
     * @param {string} event type 
     * @param {any} event data
     */
    this.emit = (type, data) => {
        this.dispatchEvent(type, data)
        return this /* chain */
    }

     /**
     * Shortcut for clearEventListeners
     * @param {string} event type 
     */
    this.clear = type => {
        this.clearEventListeners(type)
        return this
    }

    /**
     * 
     * @param {string} [type]
     */
    this.list = type => this.getEventListeners(type)
}

module.exports = Emitter

完毕!

干得好,您已成功复制粘贴我的代码!

鏂囩珷鏉ユ簮锛�https://dev.to/jochemstoel/write-your-own-chainable-event-emitter-class-for-node-and-browser-5gp
PREV
从零开始构建高效的投资组合。
NEXT
在您的网页上加载脚本