Node.js - успадкування від EventEmitter


89

Я бачу цей шаблон у багатьох бібліотеках Node.js:

Master.prototype.__proto__ = EventEmitter.prototype;

(джерело тут )

Хтось може пояснити мені на прикладі, чому це така поширена закономірність і коли це зручно?


Зверніться до цього запитання для отримання інформації stackoverflow.com/questions/5398487/…
Juicy Scripter

14
Примітка __proto__- це анти-шаблон, будь ласка, використовуйтеMaster.prototype = Object.create(EventEmitter.prototype);
Raynos

69
Власне, використовуйтеutil.inherits(Master, EventEmitter);
thesmart

1
@Raynos Що таке анти-шаблон?
starbeamrainbowlabs

1
Тепер це простіше з конструкторами класу ES6. Перевірте порівняння тут: kangax.github.io/compat-table/es6 . Перевірте документи або мою відповідь нижче.
Breedly,

Відповіді:


84

Як зазначається у коментарі вище цього коду, він буде робити Masterнаслідування з EventEmitter.prototype, тому ви можете використовувати екземпляри цього "класу" для випуску та прослуховування подій.

Наприклад, тепер ви можете зробити:

masterInstance = new Master();

masterInstance.on('an_event', function () {
  console.log('an event has happened');
});

// trigger the event
masterInstance.emit('an_event');

Оновлення : як зазначили багато користувачів, "стандартним" способом зробити це у Node буде використання "util.inherits":

var EventEmitter = require('events').EventEmitter;
util.inherits(Master, EventEmitter);

2-е оновлення : для класів ES6 рекомендується розширитиEventEmitter клас зараз:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

myEmitter.on('event', () => {
  console.log('an event occurred!');
});

myEmitter.emit('event');

Див. Https://nodejs.org/api/events.html#events_events


4
(лише невелике нагадування про те, що потрібно зробити require('events').EventEmitterспочатку - я завжди забуваю. Ось посилання на документи на випадок, якщо комусь це ще потрібно: nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil

3
ДО ВСЬОГО, домовленість для екземплярів полягає в тому, щоб MasterInstanceвводити першу літеру малими літерами, так і має бути masterInstance.
khoomeister

І як ви зберігаєте можливість перевіряти, чи: masterInstance instanceof Master?
jayarjo

3
util.inheritsробить неприємну річ, вводячи super_майно в Masterоб’єкт. Це непотрібно і намагається розглядати прототипове успадкування як класичне успадкування. Поясніть пояснення внизу цієї сторінки.
klh

2
@loretoparisi просто Master.prototype = EventEmitter.prototype;. Не потрібно суперерів. Ви також можете використовувати ES6-розширення (і це заохочується в документах Node.js util.inherits) таким чином class Master extends EventEmitter- ви отримуєте класичну форму super(), але не вводячи нічого Master.
klh

81

Спадщина класу ES6

Документи Node тепер рекомендують використовувати успадкування класів для створення власного випромінювача подій:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {
  // Add any custom methods here
}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
myEmitter.emit('event');

Примітка: Якщо ви визначаєте constructor()функцію в MyEmitter, вам слід зателефонувати super()з неї, щоб переконатися, що конструктор батьківського класу також викликається, якщо у вас немає вагомих причин цього не робити.


9
Ці зауваження є неправильними і в підсумку в цьому випадку вводять в оману. Виклик super()буде не потрібно , до тих пір , поки ви не потрібні / визначити конструктор, тому оригінальну відповідь Breedly (див історії EDIT) був абсолютно правильним. У цьому випадку ви можете скопіювати та вставити цей самий приклад у repl, повністю видалити конструктор, і він буде працювати так само. Це цілком дійсний синтаксис.
Aurelio

39

Щоб успадкувати від іншого об'єкта Javascript, зокрема EventEmitter Node.js, але взагалі будь-якого об'єкта, вам потрібно зробити дві речі:

  • надати конструктор для вашого об’єкта, який повністю ініціалізує об’єкт; у випадку, якщо ви успадковуєте якийсь інший об'єкт, ви, ймовірно, хочете делегувати частину цієї роботи з ініціалізації суперконструктору.
  • надати об’єкт-прототип, який буде використовуватися як [[proto]]об’єкт, створений із вашого конструктора; у випадку, якщо ви успадковуєте якийсь інший об'єкт, ви, ймовірно, хочете використовувати екземпляр іншого об'єкта як свій прототип.

У Javascript це складніше, ніж може здатися іншими мовами, оскільки

  • Javascript розділяє поведінку об'єкта на "конструктор" і "прототип". Ці поняття призначені для спільного використання, але можуть використовуватися і окремо.
  • Javascript - це дуже податлива мова, і люди користуються нею по-різному, і не існує єдиного справжнього визначення того, що означає "спадщина".
  • У багатьох випадках ви можете уникнути, виконавши підмножину того, що правильно, і ви знайдете безліч прикладів для наслідування (включаючи деякі інші відповіді на це питання SO), які, здається, добре підходять для вашої справи.

Для конкретного випадку Node.js's EventEmitter, ось що працює:

var EventEmitter = require('events').EventEmitter;
var util = require('util');

// Define the constructor for your derived "class"
function Master(arg1, arg2) {
   // call the super constructor to initialize `this`
   EventEmitter.call(this);
   // your own initialization of `this` follows here
};

// Declare that your class should use EventEmitter as its prototype.
// This is roughly equivalent to: Master.prototype = Object.create(EventEmitter.prototype)
util.inherits(Master, EventEmitter);

Можливі проблеми:

  • Якщо ви використовуєте set prototype для вашого підкласу (Master.prototype), з використанням або без використання util.inherits, але не викликаєте суперконструктор ( EventEmitter) для екземплярів вашого класу, вони не будуть належним чином ініціалізовані.
  • Якщо ви викликаєте суперконструктор, але не встановлюєте прототип, методи EventEmitter не працюватимуть на вашому об'єкті
  • Ви можете спробувати використовувати ініціалізований екземпляр суперкласу ( new EventEmitter), Master.prototypeзамість того, щоб конструктор підкласу Masterвикликав суперконструктор EventEmitter; залежно від поведінки конструктора суперкласу, який може здаватися, що якийсь час він працює нормально, але це не одне і те ж (і не буде працювати для EventEmitter).
  • Ви можете спробувати використовувати суперпрототип безпосередньо ( Master.prototype = EventEmitter.prototype), замість того, щоб додавати додатковий шар об’єкта через Object.create; це може здатися, що він працює нормально, доки хтось не мавпує ваш об’єкт Masterі ненавмисно також мавпує EventEmitterта всіх інших його нащадків. Кожен "клас" повинен мати свій прототип.

Знову ж таки: для успадкування від EventEmitter (або насправді будь-якого існуючого "класу" об'єкта) ви хочете визначити конструктор, який прив'язує до суперконструктора і надає прототип, який походить від супер прототипу.


19

Так виконується прототипне (прототипове?) Успадкування в JavaScript. З MDN :

Посилається на прототип об'єкта, який може бути об'єктом або нулем (що зазвичай означає, що об'єктом є Object.prototype, який не має прототипу). Іноді його використовують для реалізації пошуку властивостей на основі успадкування прототипів.

Це також працює:

var Emitter = function(obj) {
    this.obj = obj;
}

// DON'T Emitter.prototype = new require('events').EventEmitter();
Emitter.prototype = Object.create(require('events').EventEmitter.prototype);

Розуміння JavaScript ООП - одна з найкращих статей, які я останнім часом читав про ООП у ECMAScript 5.


7
Y.prototype = new X();є анти-шаблоном, будь ласка, використовуйтеY.prototype = Object.create(X.prototype);
Raynos

Це добре знати. Чи можу я десь більше прочитати? Було б цікаво, чим відрізняються отримані об'єкти.
Дафф

4
new X()створює екземпляр екземпляра X.prototypeта ініціалізує його, викликаючи Xйого. Object.create(X.prototype)просто створює екземпляр екземпляра. Ви не Emitter.prototypeхочете бути ініціалізованим. Я не можу знайти хорошої статті, яка б це пояснювала.
Райнос,

Це має сенс. Дякуємо, що вказали на це. Все ще намагаюся отримати хороші звички на Node. Браузерів просто немає для ECMA5 (і, як я зрозумів, шим не найнадійніший).
Дафф

1
Інше посилання також порушено. Спробуйте це: robotlolita.github.io/2011/10/09/…
jlukanta

5

Я вважав, що такий підхід з http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm був досить акуратним:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

У Дугласа Крокфорда теж є кілька цікавих моделей успадкування: http://www.crockford.com/javascript/inheritance.html

Я вважаю, що успадкування рідше потрібне в JavaScript та Node.js. Але при написанні програми, де успадкування може вплинути на масштабованість, я вважав би продуктивність, зважену щодо ремонтопридатності. В іншому випадку я б базував своє рішення лише на тому, які зразки ведуть до кращого загального дизайну, є більш ремонтопридатними та менш схильними до помилок.

Перевірте різні шаблони в jsPerf, використовуючи Google Chrome (V8), щоб отримати приблизне порівняння. V8 - це механізм JavaScript, який використовується як Node.js, так і Chrome.

Ось декілька jsPerfs для початку:

http://jsperf.com/prototypes-vs-functions/4

http://jsperf.com/inheritance-proto-vs-object-create

http://jsperf.com/inheritance-perf


1
Я спробував цей підхід, і обидва, emitі onвиходять як невизначені.
dopatraman

Чи не повернення (це); просто для ланцюжка?
blablabla

1

Щоб додати відповідь wprl. Він пропустив частину "прототипу":

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part

1
Насправді, ви повинні використовувати Object.create замість нового, інакше ви отримуєте стан екземпляра, а також поведінку прототипу, як це пояснювалося в іншому місці . Але краще використовувати ES6 та transpile, util.inheritsоскільки багато розумних людей будуть постійно оновлювати ці варіанти для вас.
Cool Blue
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.