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?


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


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.


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.


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.


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


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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.