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.