Node.js - dziedziczenie po EventEmitter

89

Widzę ten wzorzec w wielu bibliotekach Node.js:

Master.prototype.__proto__ = EventEmitter.prototype;

(źródło tutaj )

Czy ktoś mógłby mi wyjaśnić na przykładzie, dlaczego jest to taki powszechny wzór i kiedy jest przydatny?

jeffreyveon
źródło
Odnieś się do tego pytania, aby uzyskać informacje stackoverflow.com/questions/5398487/…
Juicy Scripter
14
Uwaga __proto__to anty-wzór, użyjMaster.prototype = Object.create(EventEmitter.prototype);
Raynos
69
Właściwie użyjutil.inherits(Master, EventEmitter);
thesmart
1
@Raynos Co to jest anty-wzór?
starbeamrainbowlabs
1
Jest to teraz łatwiejsze dzięki konstruktorom klasy ES6. Sprawdź kompatybilność tutaj: kangax.github.io/compat-table/es6 . Sprawdź dokumenty lub moją odpowiedź poniżej.
Breedly

Odpowiedzi:

84

Jak mówi powyższy komentarz, kod ten będzie Masterdziedziczyć z EventEmitter.prototype, więc możesz używać instancji tej „klasy” do emitowania i nasłuchiwania zdarzeń.

Na przykład możesz teraz zrobić:

masterInstance = new Master();

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

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

Aktualizacja : jak wskazało wielu użytkowników, 'standardowym' sposobem zrobienia tego w Node byłoby użycie 'util.inherits':

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

Druga aktualizacja : mając na uwadze klasy ES6, zaleca się EventEmitterteraz rozszerzenie klasy:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

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

myEmitter.emit('event');

Zobacz https://nodejs.org/api/events.html#events_events

alessioalex
źródło
4
(tylko małe przypomnienie, które require('events').EventEmittertrzeba zrobić najpierw - zawsze zapominam. Oto link do dokumentów na wypadek, gdyby ktoś inny tego potrzebował: nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil
3
Przy okazji, konwencją dla instancji jest małe pierwsze litery, więc MasterInstancepowinno być masterInstance.
khoomeister
A jak zachować możliwość sprawdzenia, czy: instancja instancji MasterInstance Master?
jayarjo
3
util.inheritsrobi nieprzyjemną rzecz, wprowadzając super_właściwość do Masterobiektu. Jest to niepotrzebne i próbuje traktować dziedziczenie prototypowe jak klasyczne. Spójrz na dół tej strony, aby uzyskać wyjaśnienie.
klh
2
@loretoparisi just Master.prototype = EventEmitter.prototype;. Nie ma potrzeby nadstawek. Możesz także używać rozszerzeń ES6 (i jest to zalecane w dokumentach Node.js util.inherits) w ten sposób class Master extends EventEmitter- otrzymujesz klasyczne super(), ale bez wstrzykiwania czegokolwiek Master.
klh
81

Dziedziczenie klas stylu ES6

Dokumentacja Node zaleca teraz używanie dziedziczenia klas do tworzenia własnego emitera zdarzeń:

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');

Uwaga: Jeśli definiujesz constructor()funkcję w programie MyEmitter, powinieneś wywołać super()z niej, aby upewnić się, że konstruktor klasy nadrzędnej również zostanie wywołany, chyba że masz dobry powód, aby tego nie robić.

Breedly
źródło
9
Te uwagi nie są poprawne i prowadzą w tym przypadku do wprowadzenia w błąd. Wywołanie niesuper() jest wymagane , o ile nie potrzebujesz / nie definiujesz konstruktora, stąd oryginalna odpowiedź Breedly (zobacz historię EDYCJI) była całkowicie poprawna. W takim przypadku możesz skopiować i wkleić ten sam przykład do repl, całkowicie usunąć konstruktora i będzie działać w ten sam sposób. To jest całkowicie poprawna składnia.
Aurelio,
39

Aby dziedziczyć z innego obiektu Javascript, w szczególności EventEmittera Node.js, ale w zasadzie dowolnego obiektu w ogóle, musisz zrobić dwie rzeczy:

  • dostarcz konstruktora dla swojego obiektu, który całkowicie zainicjuje obiekt; w przypadku, gdy dziedziczysz z innego obiektu, prawdopodobnie będziesz chciał delegować część tej pracy inicjalizacyjnej do super konstruktora.
  • dostarcz obiekt prototypowy, który będzie używany jako [[proto]]dla obiektów utworzonych z Twojego konstruktora; w przypadku, gdy dziedziczysz z innego obiektu, prawdopodobnie będziesz chciał użyć instancji innego obiektu jako prototypu.

Jest to bardziej skomplikowane w JavaScript, niż mogłoby się wydawać w innych językach, ponieważ

  • JavaScript rozdziela zachowanie obiektu na „konstruktor” i „prototyp”. Pojęcia te mają być używane razem, ale mogą być używane oddzielnie.
  • Javascript jest bardzo plastycznym językiem i ludzie używają go w różny sposób i nie ma jednej prawdziwej definicji tego, co oznacza „dziedziczenie”.
  • W wielu przypadkach możesz uciec od zrobienia podzbioru tego, co jest poprawne, i znajdziesz mnóstwo przykładów do naśladowania (w tym kilka innych odpowiedzi na to pytanie SO), które wydają się działać dobrze w Twoim przypadku.

Oto, co działa w konkretnym przypadku EventEmittera 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);

Możliwe słabości:

  • Jeśli używasz set the prototype dla swojej podklasy (Master.prototype), z użyciem lub bez util.inherits, ale nie wywołuj super konstruktora ( EventEmitter) dla instancji Twojej klasy, nie zostaną one poprawnie zainicjowane.
  • Jeśli wywołasz super konstruktor, ale nie ustawisz prototypu, metody EventEmitter nie będą działać na twoim obiekcie
  • Możesz spróbować użyć zainicjalizowanej instancji superklasy ( new EventEmitter) Master.prototypezamiast Masterwywoływania konstruktora nadklasy przez konstruktor podklasy EventEmitter; w zależności od zachowania konstruktora nadklasy, który może wydawać się, że działa dobrze przez jakiś czas, ale to nie to samo (i nie będzie działać dla EventEmitter).
  • Możesz spróbować użyć super prototypu bezpośrednio ( Master.prototype = EventEmitter.prototype) zamiast dodawać dodatkową warstwę obiektu poprzez Object.create; może się wydawać, że działa dobrze, dopóki ktoś nie złapie twojego obiektu Masterprzez małpę EventEmitteri nieumyślnie nie dostanie również monkeypatch i wszystkich innych jego potomków. Każda „klasa” powinna mieć swój własny prototyp.

Ponownie: aby dziedziczyć po EventEmitter (lub naprawdę dowolnej istniejącej „klasie” obiektu), chcesz zdefiniować konstruktor, który łączy się w łańcuch z super konstruktorem i dostarcza prototyp wyprowadzony z super prototypu.

metamatt
źródło
19

W ten sposób w JavaScript odbywa się dziedziczenie prototypowe (prototypowe?). Z MDN :

Odnosi się do prototypu obiektu, który może być obiektem lub wartością pustą (co zwykle oznacza, że ​​obiekt to Object.prototype, który nie ma prototypu). Czasami jest używany do implementacji wyszukiwania właściwości opartego na dziedziczeniu prototypów.

Działa to również:

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

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

Understanding JavaScript OOP to jeden z najlepszych artykułów, jakie ostatnio czytałem na temat OOP w ECMAScript 5.

Daff
źródło
7
Y.prototype = new X();jest anty-wzorem, proszę użyćY.prototype = Object.create(X.prototype);
Raynos
Dobrze wiedzieć. Czy mogę gdzieś poczytać więcej? Byłbym zainteresowany, czym różnią się powstałe obiekty.
Daff
4
new X()tworzy instancję X.prototypei inicjuje ją, wywołując Xna niej. Object.create(X.prototype)po prostu tworzy instancję. Nie chcesz Emitter.prototypebyć inicjalizowany. Nie mogę znaleźć dobrego artykułu, który to wyjaśnia.
Raynos
To ma sens. Dzięki za wskazanie tego. Wciąż próbuję nabrać dobrych nawyków w Node. Przeglądarki po prostu nie są dostępne dla ECMA5 (i jak zrozumiałem, podkładka nie jest najbardziej niezawodna).
Daff
1
Drugi link również jest uszkodzony. Spróbuj tego: robotlolita.github.io/2011/10/09/…
jlukanta
5

Pomyślałem, że to podejście z http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm było całkiem fajne:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

Douglas Crockford ma również kilka interesujących wzorców dziedziczenia: http://www.crockford.com/javascript/inheritance.html

Uważam, że dziedziczenie jest rzadziej potrzebne w JavaScript i Node.js. Ale pisząc aplikację, w której dziedziczenie może wpływać na skalowalność, rozważałbym wydajność ważoną względem łatwości konserwacji. W przeciwnym razie oparłbym swoją decyzję tylko na tym, które wzorce prowadzą do lepszych ogólnych projektów, są łatwiejsze w utrzymaniu i mniej podatne na błędy.

Przetestuj różne wzorce w jsPerf, używając Google Chrome (V8), aby uzyskać zgrubne porównanie. V8 to silnik JavaScript używany zarówno przez Node.js, jak i Chrome.

Oto kilka jsPerfs na początek:

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

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

http://jsperf.com/inheritance-perf

wprl
źródło
1
Wypróbowałem to podejście i oba emiti onsą one niezdefiniowane.
dopatraman
Czy to nie powrót (to); tylko do wiązania?
blablabla
1

Aby dodać do odpowiedzi wprl. Brakowało mu części „prototypowej”:

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part
guatedude2
źródło
1
Właściwie powinieneś użyć Object.create zamiast new, w przeciwnym razie otrzymasz stan instancji, a także zachowanie prototypu, jak wyjaśniono w innym miejscu . Ale lepiej jest używać ES6 i transpile, util.inheritsponieważ wielu inteligentnych ludzi będzie aktualizować te opcje.
Cool Blue