Node.js: heredado de EventEmitter

89

Veo este patrón en bastantes bibliotecas de Node.js:

Master.prototype.__proto__ = EventEmitter.prototype;

(fuente aquí )

¿Alguien puede explicarme con un ejemplo por qué es un patrón tan común y cuándo es útil?

Jeffreyveon
fuente
Consulte esta pregunta para obtener información stackoverflow.com/questions/5398487/…
Juicy Scripter
14
La nota __proto__es un anti-patrón, por favor useMaster.prototype = Object.create(EventEmitter.prototype);
Raynos
69
En realidad, useutil.inherits(Master, EventEmitter);
thesmart
1
@Raynos ¿Qué es un anti-patrón?
starbeamrainbowlabs
1
Esto ahora es más fácil con los constructores de clase ES6. Compruebe la compatibilidad aquí: kangax.github.io/compat-table/es6 . Consulte los documentos o mi respuesta a continuación.
Breedly

Respuestas:

84

Como dice el comentario anterior de ese código, hará Master heredará EventEmitter.prototype, por lo que puede usar instancias de esa 'clase' para emitir y escuchar eventos.

Por ejemplo, ahora podría hacer:

masterInstance = new Master();

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

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

Actualización : como señalaron muchos usuarios, la forma 'estándar' de hacerlo en Node sería usar 'util.inherits':

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

2da actualización : con las clases de ES6 sobre nosotros, se recomienda extender la EventEmitterclase ahora:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

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

myEmitter.emit('event');

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

alessioalex
fuente
4
(Solo un pequeño recordatorio para hacer require('events').EventEmitterprimero, siempre lo olvido. Aquí está el enlace a los documentos en caso de que alguien más lo necesite: nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil
3
Por cierto, la convención para las instancias es poner en minúsculas la primera letra, así MasterInstancedebería ser masterInstance.
khoomeister
¿Y cómo conserva la capacidad de verificar si: masterInstance instanceof Master?
jayarjo
3
util.inheritshace algo desagradable al inyectar la super_propiedad en el Masterobjeto. Es innecesario e intenta tratar la herencia prototípica como la herencia clásica. Eche un vistazo al final de esta página para obtener una explicación.
klh
2
@loretoparisi solo Master.prototype = EventEmitter.prototype;. No se necesitan alzas. También puede usar extensiones de ES6 (y se recomienda en los documentos de Node.js util.inherits) de esta manera class Master extends EventEmitter: obtiene clásico super(), pero sin inyectar nada en Master.
klh
81

Herencia de clase de estilo ES6

Los documentos de Node ahora recomiendan usar la herencia de clases para crear su propio emisor de eventos:

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: Si define una constructor()función en MyEmitter, debe llamar super()desde ella para asegurarse de que también se llame al constructor de la clase principal, a menos que tenga una buena razón para no hacerlo.

Raza
fuente
9
Estos comentarios no son correctos y terminan siendo engañosos en este caso. No super()se requiere llamar , siempre que no necesite / defina un constructor, por lo tanto, la respuesta original de Breedly (ver EDITAR historial) fue completamente correcta. En este caso, puede copiar y pegar este mismo ejemplo en la respuesta, eliminar el constructor por completo y funcionará de la misma manera. Esa es una sintaxis perfectamente válida.
Aurelio
39

Para heredar de otro objeto Javascript, EventEmitter de Node.js en particular, pero realmente cualquier objeto en general, necesita hacer dos cosas:

  • proporcione un constructor para su objeto, que inicializa completamente el objeto; en el caso de que esté heredando de algún otro objeto, probablemente desee delegar parte de este trabajo de inicialización al superconstructor.
  • proporcione un objeto prototipo que se utilizará como [[proto]]objeto creado a partir de su constructor; en el caso de que esté heredando de algún otro objeto, probablemente quiera usar una instancia del otro objeto como su prototipo.

Esto es más complicado en Javascript de lo que podría parecer en otros idiomas porque

  • Javascript separa el comportamiento del objeto en "constructor" y "prototipo". Estos conceptos están pensados ​​para usarse juntos, pero pueden usarse por separado.
  • Javascript es un lenguaje muy maleable y la gente lo usa de manera diferente y no existe una única definición verdadera de lo que significa "herencia".
  • En muchos casos, puede salirse con la suya haciendo un subconjunto de lo que es correcto, y encontrará toneladas de ejemplos a seguir (incluidas algunas otras respuestas a esta pregunta SO) que parecen funcionar bien para su caso.

Para el caso específico de EventEmitter de Node.js, esto es lo que funciona:

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

Posibles debilidades:

  • Si usa, configure el prototipo para su subclase (Master.prototype), con o sin usar util.inherits, pero no llame al superconstructor (EventEmitter ) para instancias de su clase, no se inicializarán correctamente.
  • Si llama al superconstructor pero no establece el prototipo, los métodos EventEmitter no funcionarán en su objeto
  • Puede intentar usar una instancia inicializada de la superclase ( new EventEmitter) en Master.prototypelugar de que el constructor de la subclase Masterllame al superconstructorEventEmitter ; dependiendo del comportamiento del constructor de la superclase, puede parecer que funciona bien por un tiempo, pero no es lo mismo (y no funcionará para EventEmitter).
  • Puede intentar usar el superprototipo directamente ( Master.prototype = EventEmitter.prototype) en lugar de agregar una capa adicional de objeto a través de Object.create; esto puede parecer que está funcionando bien hasta que alguien parchea tu objeto Mastery, sin darse cuenta, también parchea EventEmittery todos sus otros descendientes. Cada "clase" debe tener su propio prototipo.

Nuevamente: para heredar de EventEmitter (o realmente cualquier "clase" de objeto existente), desea definir un constructor que se encadena al superconstructor y proporciona un prototipo que se deriva del superprototipo.

metamatt
fuente
19

Así es como se realiza la herencia prototípica (¿prototípica?) En JavaScript. De MDN :

Se refiere al prototipo del objeto, que puede ser un objeto o nulo (lo que generalmente significa que el objeto es Object.prototype, que no tiene prototipo). A veces se utiliza para implementar búsquedas de propiedades basadas en herencia de prototipos.

Esto también funciona:

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

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

Comprender JavaScript OOP es uno de los mejores artículos que leí últimamente sobre OOP en ECMAScript 5.

Narciso
fuente
7
Y.prototype = new X();es un anti-patrón, por favor useY.prototype = Object.create(X.prototype);
Raynos
Esto es bueno saberlo. ¿Puedo leer más en alguna parte? Estaría interesado en cómo los objetos resultantes son diferentes.
Daff
4
new X()instancia una instancia de X.prototypey la inicializa invocándola X. Object.create(X.prototype)simplemente crea una instancia. No Emitter.prototypedesea inicializarse. No puedo encontrar un buen artículo que explique esto.
Raynos
Esto tiene sentido. Gracias por mencionarlo. Todavía estoy tratando de adquirir buenos hábitos en Node. Los navegadores simplemente no están ahí para ECMA5 (y según entendí, el shim no es el más confiable).
Daff
1
El otro vínculo también está roto. Prueba este: robotlolita.github.io/2011/10/09/…
jlukanta
5

Pensé que este enfoque de http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm era bastante bueno:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

Douglas Crockford también tiene algunos patrones de herencia interesantes: http://www.crockford.com/javascript/inheritance.html

Encuentro que la herencia se necesita con menos frecuencia en JavaScript y Node.js. Pero al escribir una aplicación en la que la herencia podría afectar la escalabilidad, consideraría el rendimiento contra el mantenimiento. De lo contrario, solo basaría mi decisión en qué patrones conducen a mejores diseños generales, son más fáciles de mantener y menos propensos a errores.

Pruebe diferentes patrones en jsPerf, usando Google Chrome (V8) para obtener una comparación aproximada. V8 es el motor de JavaScript utilizado por Node.js y Chrome.

Aquí hay algunos jsPerfs para comenzar:

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

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

http://jsperf.com/inheritance-perf

wprl
fuente
1
Probé este enfoque y ambos emity onaparecen como indefinidos.
dopatraman
¿No es el regreso (esto); solo para encadenar?
blablabla
1

Para agregar a la respuesta de wprl. Se perdió la parte del "prototipo":

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part
guatedude2
fuente
1
En realidad, debe usar Object.create en lugar de new; de lo contrario, obtendrá el estado de la instancia y el comportamiento en el prototipo como se explica en otra parte . Pero es mejor usar ES6 y transpile o util.inheritsya que muchas personas inteligentes mantendrán esas opciones actualizadas para usted.
Cool Blue