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);
антипаттерн
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объект. В этом нет необходимости, и мы пытаемся рассматривать прототипное наследование как классическое наследование. Взгляните на нижнюю часть этой страницы для объяснения.
02
2
@loretoparisi просто Master.prototype = EventEmitter.prototype;. Не нужны суперы. Вы также можете использовать ES6 extends (и это поощряется в документации 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) был совершенно правильным. В этом случае вы можете скопировать и вставить этот же пример в ответ, полностью удалить конструктор, и он будет работать так же. Это совершенно правильный синтаксис.
Аурелио
39

Чтобы наследовать от другого объекта Javascript, в частности от EventEmitter Node.js, но на самом деле от любого объекта в целом, вам необходимо сделать две вещи:

  • предоставьте конструктор для вашего объекта, который полностью инициализирует объект; в случае, если вы наследуете от какого-то другого объекта, вы, вероятно, захотите делегировать часть этой работы по инициализации суперконструктору.
  • предоставить объект-прототип, который будет использоваться в качестве [[proto]]объекта for, созданного из вашего конструктора; в случае, если вы наследуете от какого-то другого объекта, вы, вероятно, захотите использовать экземпляр другого объекта в качестве своего прототипа.

В Javascript это сложнее, чем может показаться на других языках, потому что

  • Javascript разделяет поведение объекта на «конструктор» и «прототип». Эти концепции предназначены для использования вместе, но могут использоваться по отдельности.
  • Javascript - очень гибкий язык, люди используют его по-разному, и нет единого истинного определения того, что означает «наследование».
  • Во многих случаях вам может сойти с рук выполнение части того, что является правильным, и вы найдете множество примеров для подражания (включая некоторые другие ответы на этот вопрос SO), которые, похоже, подходят для вашего случая.

Для конкретного случая EventEmitter Node.js вот что работает:

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 прототип для вашего подкласса (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);
антипаттерн
Это полезно знать. Могу я где-нибудь почитать больше? Было бы интересно, чем получаются разные объекты.
Daff
4
new X()создает экземпляр X.prototypeи инициализирует его, вызывая Xна нем. Object.create(X.prototype)просто создает экземпляр. Вы не хотите, Emitter.prototypeчтобы вас инициализировали. Я не могу найти хорошую статью, объясняющую это.
Raynos
Это имеет смысл. Спасибо, что указали на это. Все еще пытаясь выработать хорошие привычки на Node. Браузеров просто нет для ECMA5 (и, как я понял, прокладка не самая надежная).
Daff
1
Другая ссылка тоже не работает. Попробуйте это: robotlolita.github.io/2011/10/09/…
jlukanta
5

Я думал, что этот подход из http://www.bennadel.com/blog/2187-Exnding-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.

Вот несколько jsPerf для начала:

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

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

http://jsperf.com/inheritance-perf

wprl
источник
1
Я попробовал этот подход , и оба , emitи onпридумывают , как не определено.
допатраман
Разве это не возврат (это); просто для цепочки?
blablabla
1

Чтобы добавить к ответу wprl. Он пропустил «прототипную» часть:

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part
Guatedude2
источник
1
Фактически, вы должны использовать Object.create вместо new, иначе вы получите состояние экземпляра, а также поведение в прототипе, как описано в другом месте . Но лучше использовать ES6 и транспилировать, иначе util.inheritsмногие умные люди будут поддерживать эти параметры в актуальном состоянии.
Cool Blue