深入理解 Node.js 中 EventEmitter原始碼分析(3.0.0版本)

龍恩0707發表於2019-02-25

events模組對外提供了一個 EventEmitter 物件,即:events.EventEmitter. EventEmitter 是NodeJS的核心模組events中的類,用於對NodeJS中的事件進行統一管理,使用events可以對特定的API事件進行新增,觸發和移除等。
我們可以通過 require('events')來訪問該模組。

比如如下程式碼:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

我們先把如上程式碼放入 main.js 裡面,然後在 專案中對應目錄下 執行 node main.js 執行結果如下:

如上圖可以看到,console.log(events); 列印後,有 EventEmitter 屬性,defaultMaxListeners 屬性(getter/setter) 方法,init函式屬性,及 listenerCount 函式屬性等。

1. defaultMaxListeners的含義是:預設事件最大監聽個數,在原始碼中 預設是10個,設定最大監聽個數為10個,因為如果監聽的個數過多的話,會導致 記憶體洩露的問題產生。因此預設設定了十個。當然我們在原始碼中可以設定或者獲取最大的監聽個數,我們可以設定最大監聽的個數 如下程式碼:

// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
  if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
    throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
  }
  this._maxListeners = n;
  return this;
};

當然有設定監聽的個數,我們也有獲取最大的監聽個數,如下原始碼:

function $getMaxListeners(that) {
  if (that._maxListeners === undefined)
    return EventEmitter.defaultMaxListeners;
  return that._maxListeners;
}

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

當然原始碼中也使用了 Object.defineProperty方法監聽該屬性值是否發生改變,如下基本原始碼:

Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
  enumerable: true,
  get: function() {
    return defaultMaxListeners;
  },
  set: function(arg) {
    if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
      throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
    }
    defaultMaxListeners = arg;
  }
});

如果defaultMaxListeners值發生改變的話,就會呼叫相對應的getter/setter方法。

2. events 中的init函式中基本原始碼如下:

EventEmitter.init = function() {

  if (this._events === undefined ||
      this._events === Object.getPrototypeOf(this)._events) {
    this._events = Object.create(null);
    this._eventsCount = 0;
  }

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

如上基本程式碼:this._events 的含義是 儲存所有的事件物件,事件的觸發和事件的移除操作監聽等都在
這個物件_events的基礎上實現。

this._eventsCount 的含義是:用於統計事件的個數,也就是_events物件有多少個屬性。

this._maxListeners 的含義是:儲存最大的監聽數的含義。

3. event中的listenerCount函式的作用是:返回指定事件的監聽器數量。
如下基本程式碼:

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

EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
  var events = this._events;

  if (events !== undefined) {
    var evlistener = events[type];

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

  return 0;
}

4. events中的EventEmitter屬性

該屬性就是events對外提供的一個物件類,使用 EventEmitter 類就是對事件的觸發和監聽進行封裝。

現在我們看下建立 eventEmitter 物件,如下程式碼吧:

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

列印資訊如下:

可以看到該實列有 _events屬性及_maxListeners屬性,及該實列上的原型有很多對應的方法,比如 addListener, emit,listenerCount, listeners, on, once, removeAllListeners, removeListener, setMaxListeners 等方法,及_events 及 _maxListeners 等屬性。我們先來看下一些簡單實列上的屬性的基本原始碼如下:

// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;

一:EventEmitter基本的API使用:

1. addListener(event, listener) 為指定的事件註冊一個監聽器。該方法是on的別名。該方法接收一個事件名和一個回撥函式。
如下程式碼演示:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 註冊 kongzhi 事件
eventEmitter.on('kongzhi', function() {
  console.log('dddd'); // 列印 dddd
});

// 觸發kongzhi事件 
eventEmitter.emit('kongzhi');

2. listenerCount(eventName)

該方法返回註冊了指定事件的監聽數量。基本語法如下:

EventEmitter.listenerCount(eventName);

eventName: 指監聽的事件名

如下基本程式碼:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 註冊 kongzhi 事件
eventEmitter.on('kongzhi', function() {
  console.log('dddd1111');
});

// 註冊 kongzhi 事件
eventEmitter.on('kongzhi', function() {
  console.log('dddd22222');
});

// 觸發kongzhi事件 
eventEmitter.emit('kongzhi');

const num = eventEmitter.listenerCount('kongzhi');
console.log(num); // 返回2 說明註冊了兩個 kongzhi 這個事件

比如原始碼中如下程式碼所示:

EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
  var events = this._events;

  if (events !== undefined) {
    var evlistener = events[type];

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

如上程式碼,先判斷 this._events 是否儲存了事件,如果儲存了事件的話,如果它是個函式的話,那麼 return 1; 否則的話,如果不等於undefined,直接返回該監聽事件名的長度。其他的情況下 返回 0;

因此上面我們通過如下程式碼,就可以獲取到該監聽的事件的長度了:如下程式碼:

const num = eventEmitter.listenerCount('kongzhi');
console.log(num); // 返回2 說明註冊了兩個 kongzhi 這個事件

3. listeners(event)

該方法返回指定事件的監聽器陣列。

如下程式碼演示:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 註冊 kongzhi 事件
eventEmitter.on('kongzhi', function() {
  console.log('dddd1111');
});

// 註冊 kongzhi 事件
eventEmitter.on('kongzhi', function() {
  console.log('dddd22222');
});

// 觸發kongzhi事件 
eventEmitter.emit('kongzhi');

const num = eventEmitter.listeners('kongzhi');
console.log(num); // 返回監聽事件的陣列

num.forEach((func) => {
  func(); // 我們可以這樣指定任何一個事件呼叫,然後會分別列印 dddd1111, dddd2222
});

4. once(event, listener)
該函式的含義是:為指定事件註冊一個單次監聽器,也就是說監聽器最多隻會觸發一次,觸發後立刻解除該監聽器。
如下程式碼:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 使用on註冊監聽器
eventEmitter.on('kongzhi', function() {
  console.log('我on事件觸發了多少次呢?');
});

// 註冊 kongzhi 事件
eventEmitter.once('kongzhi', function() {
  console.log('我once事件觸發了多少次呢?');
});

// 觸發kongzhi事件 
eventEmitter.emit('kongzhi');
eventEmitter.emit('kongzhi');

如上程式碼所示:使用on 事件註冊的話,如果使用emit觸發的話,觸發了多少次,就執行多少次,使用once註冊事件的話,emit觸發多次的話,最後也只能呼叫一次,如下執行結果如下:

5. removeListener(eventName, listener)

該方法的作用是:移除指定事件的某個監聽器。監聽器必須是該事件已經註冊過的監聽器。

eventName: 事件名稱
listener: 回撥函式的名稱

如下程式碼演示:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

const callback = function() {
  console.log(11111);
};
// 使用on註冊監聽器
eventEmitter.on('kongzhi', callback);

// 觸發事件 kongzhi
eventEmitter.emit('kongzhi');

eventEmitter.removeListener('kongzhi', callback);

/* 
 我們繼續觸發事件 kongzhi 是不會觸發的,因為上面已經使用 
 removeListener 已經刪除了 kongzhi 事件了
 */
eventEmitter.emit('kongzhi');

6. removeAllListeners([event])

移除所有事件的所有監聽器,如果我們指定事件的話,則是移除指定事件的所有監聽器。

引數event: 該引數的含義是事件名稱,如果指定了該事件名稱的話,則會刪除該指定的事件名稱對應的所有函式,如果沒有指定任何事件名的話,則是刪除所有的事件。如下程式碼所示:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

const callback = function() {
  console.log(11111);
};
// 使用on註冊監聽器
eventEmitter.on('kongzhi', callback);

// 註冊kongzhi2事件
eventEmitter.on('kongzhi2', callback);

// 觸發事件 kongzhi 是可以觸發的
eventEmitter.emit('kongzhi');

// 刪除所有的監聽器

eventEmitter.removeAllListeners();

/* 
 我們繼續觸發事件 kongzhi 和 kongzhi2 是不會觸發的,因為上面已經使用 
 removeAllListeners 已經刪除了 所有的 事件了
 */
eventEmitter.emit('kongzhi');

eventEmitter.emit('kongzhi2');

7. setMaxListeners(n)

該方法的作用是 設定監聽器的預設數量。因為EventEmitters預設的監聽器為10個,如果超過10個就會輸出警告資訊,因此使用該方法,可以設定監聽器的預設數量, 使之最大的數量不會報錯。

如下程式碼所示:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

const callback = function() {
  console.log(11111);
};

// 設定預設監聽數量最多為1個,如果超過該數量,控制檯會發出警告
eventEmitter.setMaxListeners(1);

// 使用on註冊監聽器
eventEmitter.on('kongzhi', callback);

// 註冊kongzhi事件
eventEmitter.on('kongzhi', callback);

// 觸發事件 kongzhi
eventEmitter.emit('kongzhi');

執行結果如下所示:

當我們把上面的eventEmitter.setMaxListeners(2); 設定為大於1的時候,就不會報錯了,比如最大設定的預設數量為2,監聽器最大為2,就不會報錯了。

如下圖所示:



二:EventEmitter 原始碼分析

EventEmitter類它實質是一個觀察者模式的實現,什麼是觀察者模式呢?觀察者模式定義了物件間的一種一對多的關係,它可以讓多個觀察者物件同時監聽一個主題物件,當一個主題物件發生改變時,所有依賴於它的物件都會得到一個通知。
那麼在觀察者模式中最典型的實列demo就是 EventEmitter類中on和emit方法,我們可以通過on註冊多個事件進行監聽,而我們可以通過emit方法來對on事件進行觸發。比如如下demo程式碼:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

eventEmitter.on('kongzhi', function(name) {
  console.log('hello', name); // 輸出:hello 我叫空智
});

eventEmitter.emit('kongzhi', '我叫空智');

如上程式碼,我們通過eventEmitter的emit方法,發出 kongzhi 事件,然後我們通過 eventEmitter 的on方法進行監聽,從而執行相對應的函式,從而列印出資訊出來。其中on方法屬於多個觀察者那個物件,它可以有多個on方法進行監聽 emit中觸發的那個主題物件,當那個emit觸發的主體物件發生改變時,所有的on方法監聽的物件都會觸發。

上面我們知道了 EventEmitter模組的on和emit的用途後,我們首先來實現一個包含 emit和on方法的EventEmitter類。
如下程式碼:

class EventEmitter {
  constructor() {
    this._events = {}; // 儲存所有的事件
  }
  on(eventName, callback) {
    if (!this._events[eventName]) {
      this._events[eventName] = [];
    }
    this._events[eventName].push(callback);
  }
  emit(eventName, ...arg) {
    // 如果on方法監聽了多個事件的話,依次執行程式碼
    if (this._events[eventName]) {
      for (let i = 0; i < this._events[eventName].length; i++) {
        this._events[eventName][i](...arg);
      }
    }
  }
}

// 下面是例項化上面的類函式, 然後會依次執行

const eventEmitter = new EventEmitter();

eventEmitter.on('kongzhi', function(age) {
  console.log('我是空智,我今年' + age + '歲'); // 會列印:我是空智,我今年30歲
});

eventEmitter.on('kongzhi', function(age) {
  console.log('我是空智2,我今年' + age + '歲'); // 會列印:我是空智2,我今年30歲
});

eventEmitter.emit('kongzhi', 30);

如上程式碼就實現了一個類為 EventEmitter 中的on和emit方法了,on是註冊事件,emit是觸發該事件,然後執行的對應的回撥函式,首先通過on去註冊事件,然後我們會通過 this._event物件來儲存該事件的回撥函式,this._event中物件的key就是on註冊的事件名,然後on註冊的回撥函式就是 this._event中使用陣列儲存起來,該this._event的值就是一個陣列函式,然後在emit方法中,使用for迴圈進行依次執行該回撥函式。就會依次觸發對應的函式了。
emit(eventName, ...arg) 方法傳入的引數,第一個為事件名,其他引數事件對應執行函式中的實參。該方法的作用是:從事件物件中,尋找對應的key為eventName的屬性,執行該屬性所對應的陣列裡面的每一個函式。

上面是一個簡單的實現,下面我們再來看看 events.js 原始碼中是如何實現的呢?

events.js 原始碼分析:

function EventEmitter() {
  EventEmitter.init.call(this);
}
module.exports = EventEmitter;

EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;

EventEmitter.init = function() {
  /* 
   如果this._event為undefined,或 Object.getPrototypeOf 上面也沒有 events的話(es5),
   則使用Object.create()建立一個空的物件. 並且設定_eventsCount事件個數為0,也就是初始化。
   並且初始化 _maxListeners,預設為10個,未初始化之前是undefined。
  */
  if (this._events === undefined ||
      this._events === Object.getPrototypeOf(this)._events) {
    this._events = Object.create(null);
    this._eventsCount = 0;
  }

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

如上程式碼,建構函式 EventEmitter 會呼叫 EventEmitter.init方法進行初始化,然後使用 this._events = Object.create(null);建立一個物件儲存到 this._events中,作用是用於儲存和統一管理所有型別的事件,在建立建構函式的時候匯出了 EventEmitter,後面所有的方法放在該物件中的原型中。_maxListeners的含義我們上面已經講解過,是儲存最大的監聽數。預設為10個。

當然我們可以在原始碼中使用 getMaxListeners/setMaxListeners方法設定最大的監聽數,比如如下的原始碼:

EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
  if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
    throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
  }
  this._maxListeners = n;
  return this;
};

function $getMaxListeners(that) {
  if (that._maxListeners === undefined)
    return EventEmitter.defaultMaxListeners;
  return that._maxListeners;
}

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

在原始碼中,我們也對 最大的監聽數使用了 Object.defineProperty方法進行監聽,如下原始碼所示:

var defaultMaxListeners = 10;

Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
  enumerable: true,
  get: function() {
    return defaultMaxListeners;
  },
  set: function(arg) {
    if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
      throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
    }
    defaultMaxListeners = arg;
  }
});

2. addEventListener 或 on 新增事件原始碼如下:

/*
 addListener函式的別名是on,該函式有兩個引數,type是事件名稱,listener是監聽函式。
 之後會呼叫 _addListener函式進行初始化,從_addListener函式中返回的是this,這樣的設計目的是可以進行鏈式
 呼叫。
*/
EventEmitter.prototype.addListener = function addListener(type, listener) {
  return _addListener(this, type, listener, false);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;
/*
 該_addListener函式有四個引數,分別為 target指向了當前this物件。
 type為事件名稱,listener為監聽事件的函式名。
 prepend引數如果為true的話,是把監聽函式插入到陣列的頭部,預設為false,插入到陣列的尾部。
*/
function _addListener(target, type, listener, prepend) {
  var m;
  var events;
  var existing;
  /*
   如果使用on或addEventListener註冊事件時,如果listener引數不是一個函式的話,會丟擲錯誤。如下程式碼。
  */
  if (typeof listener !== 'function') {
    throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
  }
  /*
   1. 拿到當前的所有的事件 _events, 即拿到一個事件物件,物件中存放了觸發事件組或空物件。
      如果this._events是undefined的話,就使用 Object.create(null) 建立一個
      空物件給events儲存起來。然後設定 this._eventsCount = 0;  用於統計事件的個數,也就是_events物件有多少個屬性。
   2. 如果有this._event的話,如果使用了on或addEventListener註冊了 newListener 事件的話,就直接觸發執行它。
  */
  events = target._events;
  if (events === undefined) {
    events = target._events = Object.create(null);
    target._eventsCount = 0;
  } 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 object
      /*
       重新註冊events,因為 newListener鉤子可能導致this._event去重新註冊個新物件。
      */
      events = target._events;
    }
    // 儲存上一次觸發的事件
    existing = events[type];
  }
  /*
   exiting變數是儲存上一次觸發的事件物件,比如:如下測試程式碼:
    // 引入 events 模組
    const events = require('events');
    console.log(events);
    // 建立 eventEmitter 物件
    const eventEmitter = new events.EventEmitter();
    console.log(eventEmitter);

    const callback = function() {
      console.log(11111);
    };
    // 使用on註冊監聽器
    eventEmitter.on('kongzhi', callback);

    // 註冊kongzhi2事件
    eventEmitter.on('kongzhi', callback);

    // 觸發事件 kongzhi 是可以觸發的
    eventEmitter.emit('kongzhi');

    如上基本程式碼,當我們第一次監聽eventEmitter.on('kongzhi')的時候,existing並沒有儲存該事件物件函式,因此第一次
    的時候為undefined,所以會做如下判斷 如果為undefined的話,就把 callback函式賦值給 events[type]了。
    然後 target._eventsCount 自增1,也就是說 _eventsCount 儲存的事件個數加1.
    2. 如果 typeof existing 是個函式的話,說明之前註冊過一次 'kongzhi' 這樣的事件,然後繼續判斷 prepend 是否為
    true還是false,為true的話,說明把該函式插入到陣列的最前面,否則的話,插入該陣列的後面。如果為true,如下程式碼:
    events[type] = [listener, existing],否則為false的話,events[type] = [existing, listener], 其中existing
    是儲存上一次觸發的事件物件。然後所有的判斷完成後,把最新值重新賦值給 existing 變數。
    3. 如果 existing 已經是個陣列的話,並且prepend為true的話,直接插入到陣列的最前面, 因此執行程式碼:
       existing.unshift(listener);
    4. 其他的情況就是 prepend 預設為false的情況下,且 existing 為陣列的情況下,直接把 監聽函式 listener 插入到existing該陣列的後面去,如下程式碼: existing.push(listener);
  */
  if (existing === undefined) {
    // Optimize the case of one listener. Don't need the extra array object.
    existing = 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);
    }
    /*
     下面是監聽函式最大的數量。如果監聽的數量 m > 0 && existing.length(監聽數量的長度大於監聽的數量的話) > m
      && !existing.warned 的話。existing.warned 設定為false,目的是列印一次錯誤即可,因此函式內部程式碼直接設定
      為 existing.warned = true;比如如下程式碼:
      // 引入 events 模組
      const events = require('events');

      console.log(events);

      // 建立 eventEmitter 物件
      const eventEmitter = new events.EventEmitter();

      console.log(eventEmitter);

      const callback = function() {
        console.log(11111);
      };

      // 設定預設監聽數量最多為1個,如果超過該數量,控制檯會發出警告
      eventEmitter.setMaxListeners(1);

      // 使用on註冊監聽器
      eventEmitter.on('kongzhi', callback);

      // 註冊kongzhi事件
      eventEmitter.on('kongzhi', callback);

      // 觸發事件 kongzhi
      eventEmitter.emit('kongzhi');

      設定監聽最大個數為1個,但是實際監聽的個數為2,因此在控制檯中會輸出錯誤,如下報錯資訊:
      (node) warning: possible EventEmitter memory leak detected. 2 listeners added. Use emitter.setMaxListeners() to increase limit.

      如上錯誤就是下面的程式碼的new Error丟擲的。最後通過 執行如下函式丟擲,如下程式碼:
      function ProcessEmitWarning(warning) {
        if (console && console.warn) console.warn(warning);
      }
    */
    // Check for listener leak
    m = $getMaxListeners(target);
    if (m > 0 && existing.length > m && !existing.warned) {
      existing.warned = true;
      // No error code for this since it is a Warning
      // eslint-disable-next-line no-restricted-syntax
      var 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;
      ProcessEmitWarning(w);
    }
  }

  return target;
}

3. emit 觸發事件的原始碼如下:

/*
 1. 定義一個args陣列,來儲存所有的emit後面的引數。比如如下程式碼:
  // 引入 events 模組
  const events = require('events');

  console.log(events);

  // 建立 eventEmitter 物件
  const eventEmitter = new events.EventEmitter();

  console.log(eventEmitter);

  // 註冊 kongzhi 事件
  eventEmitter.on('kongzhi', function(age) {
    console.log('dddd' + age); // 列印 dddd30
  });

  // 觸發kongzhi事件 
  eventEmitter.emit('kongzhi', 30);

  如上程式碼,emit上的第二個引數傳遞了30,因此在on監聽該物件的時候,該函式會接收age這個引數值為30,因此會列印
  dddd30.
  如下程式碼:for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); 就是從第一個引數開始
  ,使用 arguments.length 獲取函式中所有的引數,依次迴圈儲存到 args陣列裡面去。注意這邊 i 是從1開始的,那麼
  位置0就是type(型別)。除了觸發型別之後所有的變數,也就是所有的引數儲存到 args陣列內部。
  2. 然後判斷 type 是否等於 error, 如 var doError = (type === 'error'); 這句程式碼會返回一個布林值。
  var events = this._events;儲存所有的事件物件,如果 type === ‘error’的話,比如 doError 為true的話,
  如果args陣列儲存的第一個值是Error的實列的話,就丟擲該error。否則的話,就丟擲如下錯誤資訊:
  events.js:62 Uncaught Error: Uncaught, unspecified "error" event. (undefined)
  如下程式碼使用 emit來監聽 error事件會列印如上的error程式碼的。如下:
  // 引入 events 模組
  const events = require('events');

  console.log(events);

  // 建立 eventEmitter 物件
  const eventEmitter = new events.EventEmitter();

  console.log(eventEmitter);

  eventEmitter.emit('error');

  如果觸發emit的事件不是error的話,那麼獲取該事件的物件,如下程式碼:var handler = events[type];
  如果該事件物件沒有函式的話,直接返回,監聽物件 on中會報錯 listener 必須為一個函式的錯誤。
  如果該handler是一個函式的話,就會執行這個函式。如:ReflectApply(handler, this, args);程式碼;
  ReflectApply 封裝的原始碼在events.js中的最上面程式碼:
  var R = typeof Reflect === 'object' ? Reflect : null
  var ReflectApply = R && typeof R.apply === 'function'
    ? R.apply
    : function ReflectApply(target, receiver, args) {
      return Function.prototype.apply.call(target, receiver, args);
    }
  想要了解 Reflect.apply的方法的話,可以看我這篇文章 (https://www.cnblogs.com/tugenhua0707/p/10291909.html#_labe2). 因此就能直接執行該回撥函式。
  2. 如果handler不是一個函式的話,而是一個陣列的話,那麼就迴圈該陣列,然後依次執行對應的函式。
*/
EventEmitter.prototype.emit = function emit(type) {
  var args = [];
  for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
  var doError = (type === 'error');

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

  // If there is no 'error' event listener then throw.
  if (doError) {
    var er;
    if (args.length > 0)
      er = args[0];
    if (er instanceof Error) {
      // Note: The comments on the `throw` lines are intentional, they show
      // up in Node's output if this results in an unhandled exception.
      throw er; // Unhandled 'error' event
    }
    // At least give some kind of context to the user
    var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));
    err.context = er;
    throw err; // Unhandled 'error' event
  }

  var handler = events[type];

  if (handler === undefined)
    return false;

  if (typeof handler === 'function') {
    ReflectApply(handler, this, args);
  } else {
    var len = handler.length;
    var listeners = arrayClone(handler, len);
    for (var i = 0; i < len; ++i)
      ReflectApply(listeners[i], this, args);
  }
  return true;
};

4. 刪除指定的事件監聽器removeListener原始碼如下:

// 如下基本程式碼:removeListener的別名是off。

EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
  var list, events, position, i, originalListener;

  /* 如果removeListener方法中第二個引數不是一個函式的話,丟擲錯誤。必須為一個函式。*/
  if (typeof listener !== 'function') {
    throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
  }
  /*
   獲取所有的事件物件,儲存到 events變數內。如果事件物件是undefined的話,直接返回。
   然後該事件物件 list = events[type]; type 是要被刪除的事件名,因此 list 就是儲存對應的函式了。
   1. 如果list物件函式等於 listener 要刪除的物件函式的話,或者 list.listener === listener 的話;
      或者匹配的是監聽事件key: Function
   如下程式碼:
   // 引入 events 模組
    const events = require('events');
    console.log(events);
    // 建立 eventEmitter 物件
    const eventEmitter = new events.EventEmitter();
    console.log(eventEmitter);
    const callback = function() {
      console.log(11111);
    };
    // 使用on註冊監聽器
    eventEmitter.on('kongzhi', callback);
    // 觸發事件 kongzhi
    eventEmitter.emit('kongzhi');
    eventEmitter.removeListener('kongzhi', callback);
    /* 
     我們繼續觸發事件 kongzhi 是不會觸發的,因為上面已經使用 
     removeListener 已經刪除了 kongzhi 事件了
     */
    eventEmitter.emit('kongzhi');
  */
  events = this._events;
  if (events === undefined)
    return this;

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

  if (list === listener || list.listener === listener) {
    /*
     如果刪除的事件相等的話,--this._eventsCount 自減1. 同時判斷 this._eventsCount 如果等於0的話,
     則移除所有監聽,這裡重置監聽物件陣列。即 this._events = Object.create(null);
    */
    if (--this._eventsCount === 0)
      this._events = Object.create(null);
    else {
      /*
       正常執行刪除:delete events[type].
       在頁面上我們可以來監聽 removeListener 事件,比如使用 removeListener 刪除事件,我們可以使用on
       來監聽 removeListener 事件,比如如下程式碼:
       // 引入 events 模組
        const events = require('events');
        console.log(events);
        // 建立 eventEmitter 物件
        const eventEmitter = new events.EventEmitter();
        console.log(eventEmitter);
        const callback = function() {
          console.log(11111);
        };
        // 使用on註冊監聽器
        eventEmitter.on('kongzhi', callback);
        // 觸發事件 kongzhi
        eventEmitter.emit('kongzhi');
        eventEmitter.on('removeListener', function(type, listener) {
          console.log('這裡是來監聽刪除事件的'); // 會列印出來
          console.log(type);  // kongzhi
          console.log(listener); // callback對應的函式
        });
        eventEmitter.removeListener('kongzhi', callback);
        /* 
         我們繼續觸發事件 kongzhi 是不會觸發的,因為上面已經使用 
         removeListener 已經刪除了 kongzhi 事件了
         */
         eventEmitter.emit('kongzhi');
      */
      delete events[type];
      if (events.removeListener)
        this.emit('removeListener', type, list.listener || listener);
    }
  } else if (typeof list !== 'function') {
    /*
     這裡是其他情況,如果list監聽的不是一個函式的話,而是一個陣列的話,比如on監聽的多個相同的事件名的時候,則遍歷該陣列,同樣判斷,如果該陣列的任何一項等於被刪除掉的 listener函式的話,
     或者陣列中的任何一項的key===listener 等於被刪除掉的listener函式的話,使用 originalListener = list[i].listener; 儲存起來,同樣使用 position = i; 儲存該對應的位置。跳出for迴圈。
     2. if (position < 0) 如果該 position 小於0的話,直接返回。說明沒有要刪除的事件。
     3. 如果position === 0的話,則刪除陣列中的第一個元素,使用 list.shift(),移除陣列中的第一個元素。
     4. 否則的話呼叫 spliceOne 方法,刪除陣列list中的對應位置的元素,該spliceOne方法程式碼如下:
       function spliceOne(list, index) {
         for (; index + 1 < list.length; index++)
           list[index] = list[index + 1];
         list.pop();
       }
    */
    position = -1;

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

    if (position < 0)
      return this;

    if (position === 0)
      list.shift();
    else {
      spliceOne(list, position);
    }
    // 如果list.length === 1 的話,events[type] = list[0]. 
    if (list.length === 1)
      events[type] = list[0];

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

  return this;
};

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;

5. 刪除所有的監聽器 removeAllListeners 原始碼如下:

/*
 removeAllListeners該函式接收一個引數type,事件名稱。
*/
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
  var listeners, events, i;

  events = this._events;
  // 如果 儲存的事件物件 events 為undefined的話,直接返回
  if (events === undefined)
    return this;

  // not listening for removeListener, no need to emit
  /*
   1. 如果 this._events儲存的事件物件的removeListener為undefined的話。說明沒有使用 removeListener來進行
   刪除事件。
   2. 如果  arguments.length === 0 則是刪除所有的事件。因為如果removeAllListeners沒有指定事件名稱的話,
   則是刪除所有的事件的。因此直接設定 this._events = Object.create(null); 為空物件,並且 this._eventsCount = 0; 監聽的事件數量為0.
   3. 如果 events[type] !== undefined 不等於undefined,說明type有的話,則是刪除該事件名的所有事件。
   --this._eventsCount 自減1. 並且如果 this._eventsCount === 0 等於0 的話,則:
   this._events = Object.create(null); 設定空物件。
   4. 如果 this._eventsCount 不等於0 的話,則刪除該指定的事件的所有事件名稱。最後返回該this物件。
  */
  if (events.removeListener === undefined) {
    if (arguments.length === 0) {
      this._events = Object.create(null);
      this._eventsCount = 0;
    } else if (events[type] !== undefined) {
      if (--this._eventsCount === 0)
        this._events = Object.create(null);
      else
        delete events[type];
    }
    return this;
  }

  // emit removeListener for all listeners on all events
  /*
   1. 如果 arguments.length === 0 等於0的話,則是刪除所有的事件。先使用 var keys = Object.keys(events);
   獲取所有的keys。然後使用for迴圈依次遍歷,得到某一個事件key。然後依次使用 this.removeAllListeners(key);
   方法遞迴呼叫刪除對應的key。
   2. 最後執行 this._events = Object.create(null); 設定為空物件。置空。
   3. this._eventsCount = 0; 事件的個數設定為0. 最後返回該this物件。
  */
  if (arguments.length === 0) {
    var keys = Object.keys(events);
    var key;
    for (i = 0; i < keys.length; ++i) {
      key = keys[i];
      if (key === 'removeListener') continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners('removeListener');
    this._events = Object.create(null);
    this._eventsCount = 0;
    return this;
  }
  /*
   1. 如果該 events[type] 是一個函式的話,則呼叫 this.removeListener(type, listeners); 刪除該事件對應
   的函式。
   2. 其他的情況就是 listeners 不等於 undefined的話。說明是一個陣列的話,那麼依次迴圈該陣列,然後依次使用
   removeListener方法刪除該事件對應的函式。最後返回this物件。使可以鏈式呼叫。
  */
  listeners = events[type];

  if (typeof listeners === 'function') {
    this.removeListener(type, listeners);
  } else if (listeners !== undefined) {
    // LIFO order
    for (i = listeners.length - 1; i >= 0; i--) {
      this.removeListener(type, listeners[i]);
    }
  }
  return this;
};

6. 返回指定事件的監聽器陣列--listeners原始碼如下:

function _listeners(target, type, unwrap) {
  var events = target._events;

  if (events === undefined)
    return [];

  var evlistener = events[type];
  if (evlistener === undefined)
    return [];
  /*
   1. 如果events[type]是一個函式的話,就返回 [evlistener.listener || evlistener], 則返回該陣列函式。
   2. 否則的話,則返回 該陣列函式。
  */
  if (typeof evlistener === 'function')
    return unwrap ? [evlistener.listener || evlistener] : [evlistener];

  return unwrap ?
    unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
}
function unwrapListeners(arr) {
  var ret = new Array(arr.length);
  for (var i = 0; i < ret.length; ++i) {
    ret[i] = arr[i].listener || arr[i];
  }
  return ret;
}
function arrayClone(arr, n) {
  var copy = new Array(n);
  for (var i = 0; i < n; ++i)
    copy[i] = arr[i];
  return copy;
}
EventEmitter.prototype.listeners = function listeners(type) {
  return _listeners(this, type, true);
};

如下程式碼演示:

// 引入 events 模組
const events = require('events');

console.log(events);

// 建立 eventEmitter 物件
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 註冊 kongzhi 事件
eventEmitter.on('kongzhi', function() {
  console.log('dddd1111');
});

// 註冊 kongzhi 事件
eventEmitter.on('kongzhi', function() {
  console.log('dddd22222');
});

// 觸發kongzhi事件 
eventEmitter.emit('kongzhi');

const num = eventEmitter.listeners('kongzhi');
console.log(num); // 返回監聽事件的陣列

num.forEach((func) => {
  func(); // 我們可以這樣指定任何一個事件呼叫,然後會分別列印 dddd1111, dddd2222
});

7. listenerCount返回註冊了指定事件的監聽數量,原始碼如下:

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

EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
  var events = this._events;

  if (events !== undefined) {
    var evlistener = events[type];

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

  return 0;
}

還有其他幾個簡單的方法 once, eventNames 可以自己看下原始碼了。

相關文章