In-depth Guangdong happy very source code download Nodejs Source-events module

Source: Internet
Author: User
Tags emit event listener

Learn the DOM should all know Guangdong happy very source download "dashengba.com" Q3266397597 an API, called AddEventListener, that is, event binding. This thing runs through the whole JS learning process, whether it is just beginning to get the DOM manual binding, or the late Vue directly @click, all the interaction can not be separated from this thing.

Similarly, in node, event binding also runs through the framework. Basically, most of the built-in modules are prototyped with events, and the following code is everywhere:

Eventemitter.call (this);
The difference is that the event binding of the DOM on the page is implemented by the browser, and the triggering is also an ' indirect ' trigger, and there is no need to actively emit the corresponding event, and there are two special properties of bubbling and capturing.

But in node, where there is no DOM, the target of the binding is an object (the DOM is also an object in nature), and inside node itself implements an event-binding and event-triggering class with pure JS.

  

The relevant source code in this article originates

First look at the constructor:

function Eventemitter () {
EventEmitter.init.call (this);
}
This invokes an Init method, which points to the calling object, and the initialization method is simple:

Copy Code
Eventemitter.init = function () {
Event Properties
if (this._events = = = Undefined | |
this._events = = = Object.getprototypeof (this). _events) {
this._events = Object.create (null);
This._eventscount = 0;
}
Maximum number of listeners of the same type of event
This._maxlisteners = This._maxlisteners | | Undefined
};
Copy Code
The three properties involved are:

1, _events = A Mount property, an empty object, responsible for collecting all types of events

2. _eventscount = record number of currently bound event types

3, _maxlisteners = the number of similar incidents listener limit

There are 3 major events related to the event, in turn.

Binding Event/on

Although the general use of APS are event.on, but in fact, with AddListener is the same:

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.addListener = function AddListener (type, listener) {
Return _addlistener (this, type, listener, false);
};
This addlistener is slightly different from Dom's AddEventListener, with the first two parameters being the same, representing the type, the callback function, respectively.

But the last parameter, which represents the precedence of inserting the event, is one way to do this:

EventEmitter.prototype.prependListener =
function Prependlistener (type, listener) {
Return _addlistener (this, type, listener, true);
};
Finally, point to this _addlistener, step to explain:

Copy Code
/**

    • Event Binding method
    • @param {Object} target object
    • @param {String} type event name
    • @param {function} Listener callback function
    • @param {Boolean} prepend is inserted
      */
      function _addlistener (target, type, listener, prepend) {
      Specifies the number of callback functions for the event type
      var m;
      Event Property Object
      VAR events;
      callback function of the corresponding type
      var existing;

      if (typeof listener!== ' function ') {
      Const Errors = Lazyerrors ();
      throw new errors. Err_invalid_arg_type (' Listener ', ' Function ', listener);
      }
      Try to get events of the corresponding type
      events = target._events;

      No corresponding event-related attributes found
      if (events = = = undefined) {
      Events = Target._events = Object.create (null);
      Target._eventscount = 0;
      }
      When an event Property object exists for an object
      else {}

      More ...

      return target;
      }
      Copy Code
      This first attempts to get the _events property of the specified object, the Mount object property initialized in the constructor.

Since the call to Eventemitter.call (this) or new Eventemitter () in any constructor will mount a _events object on the Build object, this judgment is temporarily unable to find a counter example.

Initialize one manually when it does not exist, and add a Count property to reset to 0.

When present, the processing code is as follows:

Copy Code
events = target._events;
if (events = = = undefined) {
// ...
} else {
To avoid recursion in the case that type = = = "Newlistener"! Before
Adding it to the listeners, first emit "Newlistener".
if (events.newlistener!== undefined) {
Target.emit (' Newlistener ', type,
Listener.listener? Listener.listener:listener);

// Re-assign `events` because a newListener handler could have caused the// this._events to be assigned to a new objectevents = target._events;

}
Attempt to get a collection of callback functions of the corresponding type
existing = Events[type];
}
Copy Code
The comment in this place is primarily that when the event with type Newlistener is bound, the event is triggered every time, and a recursive problem occurs if the event is bound again. Therefore, to determine whether there is a Newlistener event type, trigger the Newlistener event first if there is one.

Regardless of this, finally try to get the event listener container of the specified type, the following is the processing of existing.

Copy Code
When the type event is added for the first time
if (existing = = = undefined) {
Assign a function directly to a key of the corresponding type
existing = Events[type] = listener;
Count +1
++target._eventscount;
} else {
1. There is a corresponding type but there is only one
if (typeof existing = = = ' function ') {
Converting an array according to the prepend parameter arrangement order
existing = Events[type] =
Prepend? [Listener, existing]: [Existing, listener];
If we ' ve already got an array, just append.
}
2. There have been multiple pre-or post-insertion decisions on whether priority flag has been
else if (prepend) {
Existing.unshift (listener);
} else {
Existing.push (listener);
}

Check for listener leak
// ...
}
Copy Code
Here the processing can be very clear to see the events module for event binding processing, _events equivalent to a total object, the property key is the corresponding event type type, and key corresponding value is the corresponding listener. Only one time, the listener is used to do the value directly. The same type of event is repeatedly bound, and the value is converted to save all listener for the array. Here prepend is the last parameter, allowing the function to be inserted in front of the queue, triggering the priority.

Finally there is a number of binding events to judge:

Copy Code
Gets the _maxlisteners parameter the same type event listener the maximum number of bindings
m = $getMaxListeners (target);
Warn of possible memory leaks if exceeded
if (M > 0 && existing.length > M &&!existing.warned) {
Existing.warned = true;
Because it's warning, so there's no error code can ignore this.
Eslint-disable-next-line No-restricted-syntax
Const W = new Error (' Possible eventemitter memory leak detected. ' +
${existing.length} ${String(type)} listeners+
' added. Use Emitter.setmaxlisteners () to ' +
' Increase limit ');
W.name = ' maxlistenersexceededwarning ';
W.emitter = target;
W.type = type;
W.count = Existing.length;
Process.emitwarning (w);
}
Copy Code
Look just fine, the programmer does not have to tube warning, haha.

One-time bound event/once

There are times when you want an event to be triggered only once, and the native API does not currently exist, and jquery also encapsulates a once method that corresponds to the events module.

Copy Code
EventEmitter.prototype.once = function Once (type, listener) {
if (typeof listener!== ' function ') {
Const Errors = Lazyerrors ();
throw new errors. Err_invalid_arg_type (' Listener ', ' Function ', listener);
}
This.on (Type, _oncewrap (this, type, listener));
return this;
};
Copy Code
Apart from that judgment, the binding method is still the same, but the corresponding listener becomes a wrapper function to see.

Copy Code
function _oncewrap (target, type, listener) {
This binding object
var state = {Fired:false, wrapfn:undefined, target, type, listener};
var wrapped = oncewrapper.bind (state);
The native listener is attached to this wrapper function.
Wrapped.listener = listener;
Update the State property when finished processing
STATE.WRAPFN = wrapped;
The returned function is a wrapper.
return wrapped;
}

Function Oncewrapper (.... args) {
All this here points to the state object above
Args are derived from the parameters given when the trigger
if (!this.fired) {
Unbind the listener after the package
This.target.removeListener (This.type, THIS.WRAPFN);
This.fired = true;
Trigger Listener
Reflect.apply (This.listener, This.target, args);
}
}
Copy Code
The idea is actually similar to jquery source code, is also packaging listener, when triggering an event, first unbind this listener and then trigger the event.

It is important to note that there are two listener, one is native and the other is packaged. The binding is wrapped, so the second parameter to unbind is also wrapped. The native listener is implicitly invoked internally by the native listener property, which is attached to the wrapped function as the listener attribute, actually triggering the wrapper.

Event Trigger/emit

Watch the bindings to see the trigger.

Copy Code
EventEmitter.prototype.emit = function emit (type, ... args) {
Let Doerror = (type = = = ' ERROR ');

Const EVENTS = this._events;
Determines whether the error type event is triggered
if (Events!== undefined)
Doerror = (Doerror && events.error = = = undefined);
else if (!doerror)
return false;

If there is the No ' error ' event listener then throw.
if (doerror) {
Error handling no look
}
One thing with the existing.
Const Handler = Events[type];

if (handler = = = undefined)
return false;
If there is only one direct call
if (typeof handler = = = ' function ') {
Reflect.apply (Handler, this, args);
} else {
Multiple listener are triggered sequentially
Const LEN = handler.length;
Const LISTENERS = Arrayclone (handler, Len);
for (var i = 0; i < len; ++i)
Reflect.apply (Listeners[i], this, args);
}

return true;
};
Copy Code
Too simple, too lazy to explain.

Event Unbind/removelistener

It is also a few steps to see the process of the binding, first of all the parameter declaration:

Copy Code
Emits a ' removelistener ' event if and only if the listener is removed.
EventEmitter.prototype.removeListener = function RemoveListener (type, listener) {
List = Listener Container
Events = Event Root Object
Position = Record Delete Listener location
i = Iteration parameters
Originallistener = Native Listener reference above once
var list, events, position, I, originallistener;

if (typeof listener!== ' function ') {
Const Errors = Lazyerrors ();
throw new errors. Err_invalid_arg_type (' Listener ', ' Function ', listener);
}

events = this._events;
if (events = = = undefined)
return this;

list = Events[type];
if (list = = = undefined)
return this;

// ...
}
Copy Code
Relatively simple, the usefulness of each parameter is very obvious, after the wrong judgment, there are two different situations.

When the listener of the corresponding type is only one:

Copy Code
EventEmitter.prototype.removeListener = function RemoveListener (type, listener) {
List = Listener Container
Events = Event Root Object
Position = Record Delete Listener location
i = Iteration parameters
Originallistener = Native Listener reference above once
var list, events, position, I, originallistener;

// ...

Listener only one case.
if (list = = = Listener | | list.listener = = listener) {
If a binding event is not directly reset the _events object
if (--this._eventscount = = = 0)
this._events = Object.create (null);
else {
Delete the corresponding event type
Delete Events[type];
Attempt to trigger a RemoveListener event
if (Events.removelistener)
This.emit (' RemoveListener ', type, List.listener | | listener);
}
} else if (typeof list!== ' function ') {
// ...
}

return this;
};
Copy Code
There are two cases in which the _events object is reset if _eventscount is 0, that is, all the types are cleared.

Theoretically, according to the logic of the Else branch, when listener is left with a direct delete of the corresponding key, the last remaining is an empty object, then the heavy-duty here seems to become meaningless.

I guess the estimate is for V8 level optimization, because the object's properties in the destructive changes (adding attributes, duplicate binding and the type event causes the function to become an array of functions), the required memory will be expanded, the process is irreversible, even if there is only one Shell object, the actual occupancy is quite large. So in order to save space, here to reset, with a small space to initialize the _events object, the original space is recycled.

When the listener of the corresponding type is multiple, it is necessary to traverse.

Copy Code
if (list = = = Listener | | list.listener = = listener) {
// ...
} else if (typeof list!== ' function ') {
Position =-1;
Reverse traversal
for (i = list.length-1; I >= 0; i--) {
if (list[i] = = = Listener | | list[i].listener = = = Listener) {
Once-bound events have listener properties
Originallistener = List[i].listener;
Record location
Position = i;
Break
}
}

if (Position < 0)
return this;
When in the first position
if (position = = = 0)
List.shift ();
else {
Delete the value of an array's corresponding index
if (Spliceone = = = undefined)
Spliceone = require (' Internal/util '). Spliceone;
Spliceone (list, position);
}
If only one value in the array is converted to a single value
Sort of like a hashmap list-red black tree conversion ...
if (list.length = = = 1)
Events[type] = list[0];
Attempt to trigger RemoveListener
if (events.removelistener!== undefined)
This.emit (' RemoveListener ', type, Originallistener | | listener);
}

In-depth Guangdong happy very source code download Nodejs Source-events module

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.