PRE
This is the fourth day of my participation in the More text Challenge. For details, see more text Challenge
This section belongs to the source code Analysis column
Event
Events is a very important core module in Node.js, and many important Core apis in Node depend on it. For example, Stream is based on Events, and FS, NET, HTTP and other modules depend on Stream, so the importance of Events module can be seen.
Make a class with the basic Event method provided by Node by inheriting EventEmitter. Such objects are called Emitters, and the CB that emits events is called a listener. Unlike the events on the front end of the DOM tree, emitter does not emit events such as bubble, layer by layer capture, and there is no way to handle event passing.
Are Eventemitter’s Emit synchronous or asynchronous?
Node.js Eventemitter emit is synchronous. It is stated in the official document:
The EventListener calls all listeners synchronously in the order in which they were registered. This is important to ensure the proper sequencing of events and to avoid race conditions or logic errors.
From the point of view of design pattern, it is the application of observer pattern, and it is a mechanism to build communication relationship between different objects
About API
on (addListener)
emit
off (removeListener)
once
.
The idea is to look at the source code first, then try a simple implementation
The source code to debug
const ev = new EventEmitter() // <- debug
ev.on('e1'.() = > {
console.log('e1 1')
})
ev.on('e1'.() = > {
console.log('e1 2')
})
ev.emit('e1')
Copy the code
function EventEmitter(opts) {
EventEmitter.init.call(this, opts);
}
EventEmitter.init = function(opts) {
if (this._events === undefined ||
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = ObjectCreate(null);
this._eventsCount = 0; }...Copy the code
It’s worth noting that ObjectCreate(NULL) is used to reduce the prototype chain
Let’s go to on
ev.on('e1'.() = > {
console.log('e1 1')
})
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
function _addListener(target, type, listener, prepend) {
let m;
let events;
letexisting; . existing = events[type]; .if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
Copy the code
From here you can see that the events object was first passed in as a direct key: function
Key: Array
function _addListener(target, type, listener, prepend) {
let m;
let events;
letexisting; .return target;
Copy the code
This will return the object that new EventEmitter came out of
So you can chain calls
To emit
ev.emit('e1')
EventEmitter.prototype.emit = function emit(type, ... args) {
let doError = (type === 'error');
const events = this._events; .const handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
const result = ReflectApply(handler, this, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty
if(result ! = =undefined&& result ! = =null) {
addCatch(this, result, type, args); }}else {
const len = handler.length;
const listeners = arrayClone(handler);
for (let i = 0; i < len; ++i) {
const result = ReflectApply(listeners[i], this, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty.
// This code is duplicated because extracting it away
// would make it non-inlineable.
if(result ! = =undefined&& result ! = =null) {
addCatch(this, result, type, args); }}}return true;
}
Copy the code
First, the emit returns a result of type Bolean, indicating that the trigger succeeded or failed
Handlers of different types are processed differently
ArrayClone is a shadow Clone process
Note here that โ ๏ธ cloning is done to prevent the OFF behavior generated during the emit process from interfering with the Emit process
const listeners = arrayClone(handler);
// ->
function arrayClone(arr) {
// At least since V8 8.3, this implementation is faster than the previous
// which always used a simple for-loop
switch (arr.length) {
case 2: return [arr[0], arr[1]].case 3: return [arr[0], arr[1], arr[2]].case 4: return [arr[0], arr[1], arr[2], arr[3]].case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]].case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]];
}
return arr.slice();
}
Copy the code
Hh, an interesting implementation of arrayClone here
from
const result = ReflectApply(listeners[i], this, args);
// Here enter our own written cb execution
Copy the code
But because the arrow function passed in doesn’t have this
This in a normal function should be an instance of ev
Let’s look at what happens with OFF
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
// Emits a 'removeListener' event if and only if the listener was removed.
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
checkListener(listener);
const events = this._events;
if (events === undefined)
return this;
const list = events[type];
if (list === undefined)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = ObjectCreate(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener); }}else if (typeoflist ! = ='function') {
let position = -1;
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
position = i;
break; }}if (position < 0)
return this;
if (position === 0)
list.shift();
else {
if (spliceOne === undefined)
spliceOne = require('internal/util').spliceOne;
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0];
if(events.removeListener ! = =undefined)
this.emit('removeListener', type, listener);
}
return this;
};
Copy the code
First of all, just like on, it returns this, which also means that you can chain calls, and the rest of the processing is similar to on
Pay attention to the
If (a list [I] = = = the listener | | the list [I] listener = = = the listener) {behind this decision, the list [I]. Recycle to the listener in the treatment of the once
The deletion here also shows the data in the final _events
A related type should either correspond to a function or an array of functions
If one does not, the type is deleted
One at a time?
once
const e1_1 = () = > {
console.log('e1 1')
}
ev.once('e1', e1_1)
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener));
return this;
};
function _onceWrap(target, type, listener) {
const state = { fired: false.wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
}
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments); }}Copy the code
We’re wrapping a function
It’s a layer over the original function. It’s also a shell function bound to on
When the shell function executes
Through this. Target. RemoveListener (enclosing type, enclosing wrapFn); Remove the warpFn of the corresponding type bound to _events
First remove, then execute
How about removing the once function before emit again?
There is no original function on the callback queue, just a shell function
That’s why when you wrap a function, you add a listEnter to hold the original function
List [I].listener === listener
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
position = i;
break; }}Copy the code
Ok source here to see the same, simulation implementation
Analog implementation
During the process of writing my own emit, I felt that it was not easy to deal with the “off” happening in the emit process generated by “once”
Again to look at the source process, arrayClone is not there to understand the meaning of HHH
class MyEventEmitter {
constructor() {
this._events = {}
}
on(type, listener) {
if (this._events[type]) {
this._events[type].push(listener)
} else {
this._events[type] = [listener]
}
return this
}
emit(type, ... args) {
const listeners = this._events[type]
let flag = false
if (listeners) {
const len = listeners.length
const clonedListeners = listeners.slice()
for (let i = 0; i < len; i++) {
flag = true
clonedListeners[i].apply(this, args)
}
}
return flag
}
off(type, listener) {
const listeners = this._events[type]
if (listeners) {
const len = listeners.length
let position = -1
for (let i = len - 1; i >= 0; i--) {
if (
listeners[i] === listener ||
listeners[i].listener === listener
) {
position = i
break} } position ! = = -1 && listeners.splice(position, 1)}return this
}
static _onceWrap(target, type, listener) {
function wrapedFunction(. args) {
target.off(type, wrapedFunction)
listener.apply(target, listener)
}
wrapedFunction.listener = listener
return wrapedFunction
}
once(type, listener) {
this.on(type, MyEventEmitter._onceWrap(this, type, listener))
return this}}; (function () {
const ev = new MyEventEmitter()
const e1_1 = () = > {
console.log('e1 1')
}
ev.once('e1', e1_1)
ev.on('e1'.() = > {
console.log('e1 2')
})
ev.off('e1', e1_1)
ev.emit('e1')
ev.emit('e1')
})()
Copy the code
We’re done
END
Welcome to SedationH
Source really is the best teacher | | document ~