Node.js: eredita da EventEmitter


89

Vedo questo modello in parecchie librerie Node.js:

Master.prototype.__proto__ = EventEmitter.prototype;

(fonte qui )

Qualcuno può spiegarmi con un esempio, perché questo è un modello così comune e quando è utile?


Fare riferimento a questa domanda per informazioni stackoverflow.com/questions/5398487/…
Juicy Scripter

14
La nota __proto__è un anti-pattern, si prega di utilizzareMaster.prototype = Object.create(EventEmitter.prototype);
Raynos

69
In realtà, usautil.inherits(Master, EventEmitter);
smart

1
@ Raynos Cos'è un anti-pattern?
starbeamrainbowlabs

1
Ora è più semplice con i costruttori di classi ES6. Controlla la compatibilità qui: kangax.github.io/compat-table/es6 . Controlla i documenti o la mia risposta di seguito.
Breedly

Risposte:


84

Come dice il commento sopra quel codice, Mastererediterà da EventEmitter.prototype, quindi puoi usare istanze di quella "classe" per emettere e ascoltare eventi.

Ad esempio ora potresti fare:

masterInstance = new Master();

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

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

Aggiornamento : come molti utenti hanno sottolineato, il modo 'standard' di farlo in Node sarebbe usare 'util.inherits':

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

2 ° aggiornamento : con le classi ES6 su di noi, si consiglia di estendere la EventEmitterclasse ora:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

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

myEmitter.emit('event');

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


4
(solo un piccolo promemoria da fare require('events').EventEmitterprima - dimentico sempre. Ecco il link ai documenti nel caso qualcun altro ne abbia bisogno: nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil

3
A proposito, la convenzione per le istanze è di minuscolo la prima lettera, così MasterInstancedovrebbe essere masterInstance.
khoomeister

E come mantieni la capacità di controllare se: masterInstance instanceof Master?
jayarjo

3
util.inheritsfa una cosa sgradevole di iniettare la super_proprietà Masternell'oggetto. Non è necessario e cerca di trattare l'ereditarietà prototipica come l'eredità classica. Dai un'occhiata in fondo a questa pagina per la spiegazione.
klh

2
@loretoparisi solo Master.prototype = EventEmitter.prototype;. Non c'è bisogno di super. Puoi anche usare le estensioni ES6 (ed è incoraggiato nei documenti di Node.js su util.inherits) in questo modo class Master extends EventEmitter: ottieni il classico super(), ma senza iniettare nulla in Master.
klh

81

Ereditarietà della classe di stile ES6

La documentazione di Node ora consiglia di utilizzare l'ereditarietà della classe per creare il proprio emettitore di eventi:

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

Nota: se definisci una constructor()funzione in MyEmitter, dovresti chiamare super()da essa per assicurarti che venga chiamato anche il costruttore della classe genitore, a meno che tu non abbia una buona ragione per non farlo.


9
Questi commenti non sono corretti e finiscono per essere fuorvianti in questo caso. La chiamata nonsuper() è richiesta , a condizione che non sia necessario / definire un costruttore, quindi la risposta originale di Breedly (vedere la cronologia EDIT) era del tutto corretta. In questo caso puoi copiare e incollare questo stesso esempio nella sostituzione, rimuovere completamente il costruttore e funzionerà allo stesso modo. Questa è una sintassi perfettamente valida.
Aurelio

39

Per ereditare da un altro oggetto Javascript, in particolare l'EventEmitter di Node.js, ma in realtà qualsiasi oggetto in generale, devi fare due cose:

  • fornire un costruttore per il tuo oggetto, che inizializza completamente l'oggetto; nel caso in cui tu stia ereditando da qualche altro oggetto, probabilmente vorrai delegare parte di questo lavoro di inizializzazione al super costruttore.
  • fornire un oggetto prototipo che verrà utilizzato come [[proto]]oggetto for creato dal costruttore; nel caso in cui tu stia ereditando da qualche altro oggetto, probabilmente vorrai utilizzare un'istanza dell'altro oggetto come prototipo.

Questo è più complicato in Javascript di quanto potrebbe sembrare in altre lingue perché

  • Javascript separa il comportamento degli oggetti in "costruttore" e "prototipo". Questi concetti sono pensati per essere usati insieme, ma possono essere usati separatamente.
  • Javascript è un linguaggio molto malleabile e le persone lo usano in modo diverso e non esiste un'unica vera definizione di cosa significhi "eredità".
  • In molti casi, puoi cavartela facendo un sottoinsieme di ciò che è corretto e troverai tonnellate di esempi da seguire (comprese alcune altre risposte a questa domanda SO) che sembrano funzionare bene per il tuo caso.

Per il caso specifico di EventEmitter di Node.js, ecco cosa funziona:

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

Possibili svantaggi:

  • Se usi set the prototype per la tua sottoclasse (Master.prototype), con o senza utilizzo util.inherits, ma non chiami il super costruttore ( EventEmitter) per le istanze della tua classe, non verranno inizializzate correttamente.
  • Se chiami il super costruttore ma non imposti il ​​prototipo, i metodi EventEmitter non funzioneranno sul tuo oggetto
  • Potresti provare a usare un'istanza inizializzata della superclasse ( new EventEmitter) Master.prototypeinvece di fare in modo che il costruttore della sottoclasse Masterchiami il super costruttore EventEmitter; a seconda del comportamento del costruttore della superclasse che potrebbe sembrare che funzioni bene per un po ', ma non è la stessa cosa (e non funzionerà per EventEmitter).
  • Potresti provare a utilizzare direttamente il super prototipo ( Master.prototype = EventEmitter.prototype) invece di aggiungere un ulteriore livello di oggetto tramite Object.create; questo potrebbe sembrare che funzioni bene fino a quando qualcuno non prende il tuo oggetto Mastere inavvertitamente ha anche applicato il patch di scimmia EventEmittere tutti i suoi discendenti. Ogni "classe" dovrebbe avere il proprio prototipo.

Ancora una volta: per ereditare da EventEmitter (o in realtà qualsiasi "classe" di oggetti esistente), si desidera definire un costruttore che si concatena al super costruttore e fornisce un prototipo derivato dal super prototipo.


19

Questo è il modo in cui l'ereditarietà prototipica (prototipica?) Viene eseguita in JavaScript. Da MDN :

Si riferisce al prototipo dell'oggetto, che può essere un oggetto o null (che di solito significa che l'oggetto è Object.prototype, che non ha prototipo). A volte viene utilizzato per implementare la ricerca di proprietà basata sull'ereditarietà del prototipo.

Funziona anche questo:

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

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

Capire JavaScript OOP è uno dei migliori articoli che ho letto ultimamente su OOP in ECMAScript 5.


7
Y.prototype = new X();è un anti-pattern, si prega di utilizzareY.prototype = Object.create(X.prototype);
Raynos

Buono a sapersi. Posso leggere di più da qualche parte? Sarebbe interessato come gli oggetti risultanti siano diversi.
Daff

4
new X()crea un'istanza di X.prototypee la inizializza invocandola X. Object.create(X.prototype)crea solo un'istanza. Non vuoi Emitter.prototypeessere inizializzato. Non riesco a trovare un buon articolo che spieghi questo.
Raynos

Questo ha senso. Grazie per segnalarlo. Sto ancora cercando di prendere buone abitudini su Node. I browser non sono lì per ECMA5 (e come ho capito lo shim non è il più affidabile).
Daff

1
Anche l'altro collegamento è interrotto. Prova questo: robotlolita.github.io/2011/10/09/…
jlukanta

5

Ho pensato che questo approccio da http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm fosse abbastanza pulito:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

Douglas Crockford ha anche alcuni interessanti modelli di ereditarietà: http://www.crockford.com/javascript/inheritance.html

Trovo che l'ereditarietà sia meno spesso necessaria in JavaScript e Node.js. Ma nello scrivere un'app in cui l'ereditarietà potrebbe influire sulla scalabilità, considererei le prestazioni valutate rispetto alla manutenibilità. Altrimenti, baserei la mia decisione solo su quali modelli portano a progetti complessivi migliori, sono più gestibili e meno soggetti a errori.

Prova diversi modelli in jsPerf, utilizzando Google Chrome (V8) per ottenere un confronto approssimativo. V8 è il motore JavaScript utilizzato sia da Node.js che da Chrome.

Ecco alcuni jsPerfs per iniziare:

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

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

http://jsperf.com/inheritance-perf


1
Ho provato questo approccio ed entrambi emite onrisultano indefiniti.
dopatraman

Non è il ritorno (questo); solo per concatenare?
blablabla

1

Da aggiungere alla risposta di wprl. Gli mancava la parte "prototipo":

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

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

1
In realtà, dovresti usare Object.create invece di new altrimenti ottieni lo stato dell'istanza e il comportamento sul prototipo come spiegato altrove . Ma è meglio usare ES6 e transpile o util.inheritspoiché molte persone intelligenti manterranno aggiornate queste opzioni per te.
Cool Blue
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.