eventEmitter3源码剖析与进修

背景

事宜监听在前端的开辟历程当中是一个很罕见的状况。DOM上的事宜监听体式格局,让我们看到了经由历程事宜的体式格局来举行详细的营业逻辑的处置惩罚的方便。

在详细的一些营业场景中,第三方的自定义事宜能够在层级较多,函数挪用难题以及须要多个处所相应的时刻有着其奇特的上风——挪用轻易,防止多层嵌套,下降组件间耦合性。

这篇文章所提到的EventEmitter3,就是一个典范的第三方事宜库,能够让我们经由历程自定义的实践来完成多个函数与组件间的通讯。

团体构造图

EventEmitter3的设想较为的简朴,详细构造能够看下图所示。

《eventEmitter3源码剖析与进修》

下面我们将根据常人的一般思绪来对这个构造举行引见。

各部分构造与功用

EE

function EE(fn, context, once) {
    this.fn = fn;
    this.context = context;
    this.once = once || false;
}

从类EE的代码中我们能够很明白的相识到,第一个参数为回调函数,第二个参数为回调函数的上下文,第三个参数是一个once的标志位。因为代码简朴,在这里就简朴引见下了。

Prototype属性

events

该要领用于存储eventEmitter的全部事宜称号与回调函数的鸠合,初始值为undefined。

Prototype要领

eventName

  • 作用:返回当前已注册的事宜称号的列表

  • 参数:无

listeners

  • 作用:返回某一个事宜称号的一切监听函数

  • 参数:event——事宜称号,exists——是不是只推断存在与否

emit

  • 作用:触发某个事宜

  • 参数:event——事宜名,a1~a5——参数1~5

on

  • 作用:为某个事宜增加一个监听函数

  • 参数:event——事宜名,fn——回调函数,context——上下文

once

  • 作用:相似on,辨别在于该函数只会触发一次

  • 参数:event——事宜名,fn——回调函数,context——上下文

removeListner

  • 作用:移除某个事宜的监听函数

  • 参数:event——事宜名,fn——事宜监听函数,context——只移除上下文婚配的事宜监听函数,once——只移除范例婚配的事宜监听函数

removeAllListener

  • 作用:移除某个时候的一切监听函数

  • 参数:event——事宜名

进修思绪

下面我们将从增加监听函数, 事宜触发与删除监听函数来举行详细的代码剖析,从而相识该库的完成思绪。

事宜对象

详细代码以下所示:

 //一个单一的事宜监听函数的单位
 //
 // @param {Function} fn Event handler to be called. 回调函数
 // @param {Mixed} context Context for function execution. 函数实行上下文
 // @param {Boolean} [once=false] Only emit once 是不是实行一次的标志位
 // @api private 私有API
 
function EE(fn, context, once) {
    this.fn = fn;
    this.context = context;
    this.once = once || false;
}

该类为eventEmitter中用于存储事宜监听函数的最小类。

增加监听函数

on函数详细代码以下所示:

 // Register a new EventListener for the given event.
 // 注册一个指定的事宜的事宜监听函数
 //
 // @param {String} event Name of the event. 事宜名
 // @param {Function} fn Callback function. 回调函数
 // @param {Mixed} [context=this] The context of the function. 上下文
 // @api public 公有API
 
 EventEmitter.prototype.on = function on(event, fn, context) {
    var listener = new EE(fn, context || this)
        , evt = prefix ? prefix + event : event;

    if (!this._events) this._events = prefix ? {} : Object.create(null);
    if (!this._events[evt]) {
        this._events[evt] = listener;//第一次存储为一个事宜监听对象
    } else {
        if (!this._events[evt].fn) {//第三次及今后则直接向对象数组中增加事宜监听对象
            this._events[evt].push(listener);
        } else {//第二次将存储的对象与新对象转换为事宜监听对象数组
            this._events[evt] = [
                this._events[evt], listener
            ];
        }
    }

    return this;
}

当我们向事宜E增加函数F时,会挪用on要领,此时on要领会搜检eventEmitter中prototype属性events的E属性。

  • 当这个属性为undefined时,直接将该函数地点的事宜对象赋值给evt属性。

  • 当该属性当前值为一个对象且其函数fn不等于函数F时,则会将其转换为一个包括这两个事宜对象的事宜对象数组。

  • 当这个属性已是一个对象数组时,则直接经由历程push要领向数组中增加对象。

prefix是用来推断Object.create()要领是不是存在,假如存在则直接挪用该要领来建立属性,不然经由历程在属性前增加~来防止掩盖原有属性。

once的函数完成与on函数基础一致,所以在此就不再举行剖析。

触发监听函数

emit函数代码以下所示:

// Emit an event to all registered event listeners.
// 触发已注册的事宜监听函数
//
// @param {String} event The name of the event. 事宜名
// @returns {Boolean} Indication if we've emitted an event. 假如触发事宜胜利,则返回true,不然返回false
// @api public 公有API

EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
    var evt = prefix ? prefix + event : event;

    if (!this._events || !this._events[evt]) return false;

    var listeners = this._events[evt]
        , len = arguments.length
        , args
        , i;

    if ('function' === typeof listeners.fn) {
        if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);

        switch(len) {
            case 1:
                return listeners.fn.call(listeners.context), true;
            case 2:
                return listeners.fn.call(listeners.context, a1), true;
            case 3:
                return listeners.fn.call(listeners.context, a1, a2), true;
            case 4:
                return listeners.fn.call(listeners.context, a1, a2, a3), true;
            case 5:
                return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
            case 6:
                return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
        }

        for(i = 1, args = new Array(len - 1); i < len; i++) {
            args[i - 1] = arguments[i];
        }

        listeners.fn.apply(listeners.context, args);
    } else {
        //因为篇幅缘由省略E属性为数组时经由历程轮回挪用来完成事宜触发的历程
    }

    return true;
};

当我们触发事宜E时,我们只须要挪用emit要领。该要领会自动检索事宜E中一切的事宜监听对象,触发一切的事宜监听函数,同时移撤除经由历程once增加,只须要触发一次的事宜监听函数。

移除事宜监听函数

removeListener函数代码以下:

// Remove event listeners.
// 移除事宜监听函数
//
// @param {String} event The event we want to remove. 须要被移除的事宜名
// @param {Function} fn The listener that we need to find. 须要被移除的事宜监听函数
// @param {Mixed} context Only remove listeners matching this context. 只移除婚配该参数指定的上下文的监听函数
// @param {Boolean} once Only remove once listeners. 只移除婚配该参数指定的once属性的监听函数
// @api public 大众API

EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
    var evt = prefix ? prefix + event : event;

    if (!this._events || !this._events[evt]) return this;

    var listeners = this._events[evt]
        , events = [];

    if (fn) {
        if (listeners.fn) {
            if (
                listeners.fn !== fn
                || (once && !listeners.once)
                || (context && listeners.context !== context)
            ) {
                events.push(listeners);
            }
        } else {
            //因为篇幅缘由省去方便listeners属性查找函数删除的代码
        }
    }

    //
    // Reset the array, or remove it completely if we have no more listeners.
    //
    if (events.length) {
        this._events[evt] = events.length === 1 ? events[0] : events;
    } else {
        delete this._events[evt];
    }

    return this;
};

removeListener函数完成较为简朴。当我们须要移除事宜E的某个函数时,它运用一个event属性来保留不须要被移除的事宜监听对象,然后方便全部事宜监听数组(单个时为对象),而且末了将event属性的值赋值给E属性从而掩盖掉原有的属性,到达删除的目标。

其他

该库中另有一些其他的函数,因为对全部库的明白不发生太大影响,因而没有在此举行解说,有须要的能够前去我的github堆栈举行检察。

瑕玷

eventEmitter的代码虽然构造清楚,然则依然存在一些问题。比方ononce要领的完成中,只要一个属性差别,其他代码都如出一辙,实在能够抽出一个特定的函数来举行处置惩罚,经由历程属性来举行辨别挪用即可。

同时,在同一个函数比方emit中,也存在大批的反复代码,能够举行进一步的笼统和整顿,使得代码越发简朴。

总结

eventEmitter第三方事宜库从完成上来看较为简朴,而且构造清楚轻易浏览,引荐有兴致的能够花约莫一个小时的时候来进修下。

附录

本人github地点——eventEmitter3
官方github地点

    原文作者:hjava
    原文地址: https://segmentfault.com/a/1190000005084960
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞