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?


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


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ć.


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.


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.


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


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.