最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Explanation of Node.js event emitter source code - Stack Overflow

programmeradmin6浏览0评论

I am looking at the source for Node.js event emitter

.js

I am trying to figure out how the code identifies a function, specifically when using addListener/removeListener

those functions both accept a signature of (String s, Function f)

but what I don't understand is how they identify which function to remove when removeListener is called, as there may be multiple functions that serve as callbacks for the same event.

I suppose I am specifically wondering how this line

list[i] === listener

that is, paring two functions for equality, works in JS

I am looking at the source for Node.js event emitter

https://github./nodejs/node/blob/master/lib/events.js

I am trying to figure out how the code identifies a function, specifically when using addListener/removeListener

those functions both accept a signature of (String s, Function f)

but what I don't understand is how they identify which function to remove when removeListener is called, as there may be multiple functions that serve as callbacks for the same event.

I suppose I am specifically wondering how this line

list[i] === listener

that is, paring two functions for equality, works in JS

Share Improve this question edited Feb 26, 2016 at 22:03 Alexander Mills asked Feb 26, 2016 at 20:05 Alexander MillsAlexander Mills 100k166 gold badges537 silver badges918 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 6

but what I don't understand is how they identify which function to remove when removeListener is called, as there may be multiple functions that serve as callbacks for the same event.

An eventEmitter object (or anything that inherits from it) stores a map of all the event names that it is managing listeners for. It can then store an array of functions for each event name in the map. addListener() adds a function to the right list and removeListener() removes the matching function from the right list. When you do:

obj.addListener("someEvent", someFunction);

The eventEmitter object is making sure that "someEvent" is in the map of event names that it is managing and it is adding someFunction to an array of listeners for that specific event name. There can be more than one listener for a given event name so anytime there is more than one, the eventEmitter uses an array so it can store all the listener functions for that specific event.

The code for both addListener() and removeListener() is plicated quite a bit by an optimization that both implement and that makes it more difficult to follow the code. If there is more than one listener for a given event, then the code stores an array of listener functions in the event map. But, if there is only one listener, then it just stores the one listener (no array). That means that any code that uses the listener list has to first check if it's a single listener or an array of listeners.

removeListener() takes two arguments, an event type and a function. The goal is to find a previously registered listener for that event that registered that specific function and remove them.

The emitter object itself stores an array of functions for each type of event. So, when removeListener(type, listener) is called, the caller is passing in an event type and a specific function. The eventEmitter code will look in its data to find the list of listeners for the specific type of event that was passed in and then search that list of listeners for one that matches the specific listener that was passed in. If it is found, it will be removed.

Here's an annotated copy of the code that should explain what's going on in each block of code within the removeListener() function:

// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener =
    function removeListener(type, listener) {
      var list, events, position, i;

      // make sure that listener was passed in and that it's a function
      if (typeof listener !== 'function')
        throw new TypeError('"listener" argument must be a function');

      // get the map of events we have listeners for
      events = this._events;
      if (!events)
        return this;

      // get the list of functions for the specific event that was passed in
      list = events[type];
      if (!list)
        return this;

      // handle some special cases when there is only one listener for an event
      if (list === listener || (list.listener && list.listener === listener)) {
        if (--this._eventsCount === 0)
          this._events = {};
        else {
          delete events[type];
          if (events.removeListener)
            this.emit('removeListener', type, listener);
        }
      } else if (typeof list !== 'function') {
        // when not a special case, we will have to find the right
        // function in the array so initialize our position variable
        position = -1;

        // search backward through the array of functions to find the
        // matching function
        for (i = list.length; i-- > 0;) {
          if (list[i] === listener ||
              (list[i].listener && list[i].listener === listener)) {
            position = i;
            break;
          }
        }

        // if we didn't find it, nothing to do so just return
        if (position < 0)
          return this;

        // if the list has only one function in it, then just clear the list
        if (list.length === 1) {
          list[0] = undefined;
          if (--this._eventsCount === 0) {
            this._events = {};
            return this;
          } else {
            delete events[type];
          }
        } else {
          // remove that one function from the array
          spliceOne(list, position);
        }

        // send out an event if we actually removed a listener
        if (events.removeListener)
          this.emit('removeListener', type, listener);
      }

      return this;
    };

Added Explanation based on ments:

Functions in Javascript are first class objects. When code uses either == or === to pare two functions or to pare a variable to a function reference, Javascript just pares to see if each operand refers to the same underlying Javascript object. There is no .toString() being used here. It's just testing to see if they refer to the same physical object.

Here are a couple examples:

function myFunc() {
   console.log("hello");
}

var a = myFunc;
if (a === myFunc) {
    console.log("Yes, a does refer to myFunc");
}

var b = a;
if (b === a) {
    console.log("Yes, a and b refer to the same function");
}

function myFunc2() {
   console.log("hello");
}

a = myFunc;
b = myFunc2;

if (a !== b) {
    console.log("a and b do not refer to the same function");
}

Or, a little more like what is being used in addListener() and removeListener() in a working snippet:

function myFunc() {
   console.log("hello");
}

var list = [];
log("Items in list initially: " + list.length);
list.push(myFunc);
log("Items in list after adding: " + list.length);

// search through list to find and remove any references to myFunc
for (var i = list.length - 1; i >= 0; i--) {
    if (list[i] === myFunc) {
         list.splice(i, 1);
    }
}

log("Items in list after find/remove: " + list.length);

// helper function to log output
function log(x) {
    var div = document.createElement("div");
    div.innerHTML = x;
    document.body.appendChild(div);
}

The code finds the function by doing parisons similar to the following:

var events = [/*...*/];
function removeListener(type, fn){
    for(var z = 0; z < events.length; z++){
         if(events[z] === fn){
             // fn event listener is equal to
             // the zth element of events, so
             // remove this element
         }
    }
}

addListener and removeListener require you to pass in a function. That function gets stored in a dictionary that maps the specific event to the function(s) registered for that event.

In order for removeListener to work, you have to pass in the same function that was passed into addListener. Understand that in JavaScript, functions are first-class citizens. In simple terms, this means you can assign a function to a variable and pass functions as arguments to other functions.

Here's an example of using addListener and then removing with removeListener,

var cb = function() {
  console.log('event emitted');
}

emitter.addListener('event', cb);

// We can then remove the association by passing in cb as the
// second argument to removeListener
emitter.removeListener('event', cb);

Alternatively, we can use named functions to avoid assigning the function to a variable.

emitter.addListener('event', function cb() {
  console.log('event emitted');
});

// Remove
emitter.removeListener('event', cb);

Since functions are first-class citizens, they can be stored and pared directly. Using this, removeListener just iterates its internal list until it finds the particular function object.

发布评论

评论列表(0)

  1. 暂无评论