2017年3月3日 星期五

(js)nodejs => EventEmitter 概觀

function EventEmitter() {

    /**
     * 賦予 this 個別屬性
     * 壓過 prototype
     *
     * domain
     * _events
     * _eventsCount
     * _maxListeners
     *
     */
    EventEmitter.init.call(this);
}

/**
 * 類別方法
 */
(function(fn) {
    fn.usingDomains = false;
    fn.defaultMaxListeners = 10;


    fn.init = function() {
        this.domain = null;

        if (EventEmitter.usingDomains) {
            // if there is an active domain, then attach to it.
            domain = domain || require('domain');
            if (domain.active && !(this instanceof domain.Domain)) {
                this.domain = domain.active;
            }
        }

        if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
            this._events = new EventHandlers();
            this._eventsCount = 0;
        }

        this._maxListeners = this._maxListeners || undefined;
    };


    fn.listenerCount = function(emitter, type) {
        if (typeof emitter.listenerCount === 'function') {
            return emitter.listenerCount(type);
        } else {
            return listenerCount.call(emitter, type);
        }
    };
})(EventEmitter);



(function() {
    this.domain;
    this._events;
    this._maxListeners;

    this.setMaxListeners = function(n) {
        if (typeof n !== 'number' || n < 0 || isNaN(n))
            throw new TypeError('"n" argument must be a positive number');
        this._maxListeners = n;
        return this;
    };

    this.getMaxListeners = function() {
        return $getMaxListeners(this);
    }

    this.emit = function(type) {
        var er, handler, len, args, i, events, domain;
        var needDomainExit = false;
        var doError = (type === 'error');

        events = this._events;
        if (events)
            doError = (doError && events.error == null);
        else if (!doError)
            return false;

        domain = this.domain;

        // If there is no 'error' event listener then throw.
        if (doError) {
            er = arguments[1];
            if (domain) {
                if (!er)
                    er = new Error('Uncaught, unspecified "error" event');
                er.domainEmitter = this;
                er.domain = domain;
                er.domainThrown = false;
                domain.emit('error', er);
            } else if (er instanceof Error) {
                throw er; // Unhandled 'error' event
            } else {
                // At least give some kind of context to the user
                var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
                err.context = er;
                throw err;
            }
            return false;
        }

        handler = events[type];

        if (!handler)
            return false;

        if (domain && this !== process) {
            domain.enter();
            needDomainExit = true;
        }

        var isFn = typeof handler === 'function';
        len = arguments.length;
        switch (len) {
            // fast cases
            case 1:
                emitNone(handler, isFn, this);
                break;
            case 2:
                emitOne(handler, isFn, this, arguments[1]);
                break;
            case 3:
                emitTwo(handler, isFn, this, arguments[1], arguments[2]);
                break;
            case 4:
                emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
                break;
                // slower
            default:
                args = new Array(len - 1);
                for (i = 1; i < len; i++)
                    args[i - 1] = arguments[i];
                emitMany(handler, isFn, this, args);
        }

        if (needDomainExit) {
            domain.exit();
        }
        return true;
    }

    this.addListener = function(type, listener) {
        return _addListener(this, type, listener, false);
    };

    this.prependListener = function(type, listener) {
        return _addListener(this, type, listener, true);
    };

    this.once = function(type, listener) {
        if (typeof listener !== 'function') {
            throw new TypeError('"listener" argument must be a function');
        }

        this.on(type, _onceWrap(this, type, listener));
        return this;
    };


    this.prependOnceListener = function(type, listener) {
        if (typeof listener !== 'function')
            throw new TypeError('"listener" argument must be a function');
        this.prependListener(type, _onceWrap(this, type, listener));
        return this;
    };


    function removeListener(type, listener) {
        var list, events, position, i, originalListener;

        if (typeof listener !== 'function') {
            throw new TypeError('"listener" argument must be a function');
        }

        // this._events = {}
        events = this._events;
        if (!events) {
            return this;
        }
        // [fn, fn]
        list = events[type];

        if (!list) {
            return this;
        }

        if (list === listener || list.listener === listener) {
            if (--this._eventsCount === 0)
                this._events = new EventHandlers();
            else {
                delete events[type];
                if (events.removeListener)
                    this.emit('removeListener', type, list.listener || listener);
            }
        } else if (typeof list !== 'function') {
            // list 是陣列
            position = -1;

            // core
            for (i = list.length; i-- > 0;) {
                if (list[i] === listener || list[i].listener === listener) {
                    originalListener = list[i].listener;
                    position = i;
                    break;
                }
            }

            if (position < 0) {
                return this;
            }


            if (list.length === 1) {
                list[0] = undefined;
                if (--this._eventsCount === 0) {
                    this._events = new EventHandlers();
                    return this;
                } else {
                    delete events[type];
                }
            } else {
                spliceOne(list, position);
            }

            if (events.removeListener)
                this.emit('removeListener', type, originalListener || listener);
        }

        return this;
    };


    this.removeAllListeners = function(type) {
        var listeners, events;

        events = this._events;
        if (!events) {
            return this;
        }


        // not listening for removeListener, no need to emit
        if (!events.removeListener) {
            if (arguments.length === 0) {
                this._events = new EventHandlers();
                this._eventsCount = 0;
            } else if (events[type]) {
                if (--this._eventsCount === 0)
                    this._events = new EventHandlers();
                else
                    delete events[type];
            }
            return this;
        }

        // emit removeListener for all listeners on all events
        if (arguments.length === 0) {
            var keys = Object.keys(events);
            for (var i = 0, key; i < keys.length; ++i) {
                key = keys[i];
                if (key === 'removeListener') continue;
                this.removeAllListeners(key);
            }
            this.removeAllListeners('removeListener');
            this._events = new EventHandlers();
            this._eventsCount = 0;
            return this;
        }

        listeners = events[type];

        if (typeof listeners === 'function') {
            this.removeListener(type, listeners);
        } else if (listeners) {
            // LIFO order
            do {
                this.removeListener(type, listeners[listeners.length - 1]);
            } while (listeners[0]);
        }

        return this;
    };


    this.listeners = function(type) {
        var evlistener;
        var ret;
        var events = this._events;

        if (!events) {
            ret = [];
        } else {
            evlistener = events[type];
            if (!evlistener) {
                ret = [];

            } else if (typeof evlistener === 'function') {
                ret = [evlistener];

            } else
                ret = arrayClone(evlistener, evlistener.length);
        }

        return ret;
    };


    this.listenerCount = function(type) {
        const events = this._events;

        if (events) {
            const evlistener = events[type];

            if (typeof evlistener === 'function') {
                return 1;
            } else if (evlistener) {
                return evlistener.length;
            }
        }
        return 0;
    };


    this.eventNames = function() {
        return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
    };
}).call(EventEmitter.prototype);