Node.js - héritant d'EventEmitter

89

Je vois ce modèle dans de nombreuses bibliothèques Node.js:

Master.prototype.__proto__ = EventEmitter.prototype;

(source ici )

Quelqu'un peut-il m'expliquer avec un exemple, pourquoi c'est un modèle si courant et quand c'est pratique?

Jeffreyveon
la source
Reportez-vous à cette question pour plus d'informations stackoverflow.com/questions/5398487/…
Juicy Scripter
14
Remarque __proto__est un anti-motif, veuillez utiliserMaster.prototype = Object.create(EventEmitter.prototype);
Raynos
69
En fait, utilisezutil.inherits(Master, EventEmitter);
thesmart
1
@Raynos Qu'est-ce qu'un anti-pattern?
starbeamrainbowlabs
1
C'est maintenant plus facile avec les constructeurs de classe ES6. Vérifiez la compatibilité ici: kangax.github.io/compat-table/es6 . Consultez les documents ou ma réponse ci-dessous.
Breedly

Réponses:

84

Comme le commentaire ci-dessus ce code l'indique, il Masterhéritera de EventEmitter.prototype, vous pouvez donc utiliser des instances de cette «classe» pour émettre et écouter des événements.

Par exemple, vous pouvez maintenant faire:

masterInstance = new Master();

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

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

Mise à jour : comme de nombreux utilisateurs l'ont souligné, la manière `` standard '' de faire cela dans Node serait d'utiliser `` util.inherits '':

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

2ème mise à jour : avec les classes ES6 à nos portes, il est recommandé d'étendre la EventEmitterclasse maintenant:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

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

myEmitter.emit('event');

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

alessioalex
la source
4
(juste un petit rappel à faire en require('events').EventEmitterpremier, j'oublie toujours. Voici le lien vers la documentation au cas où quelqu'un d'autre en aurait besoin: nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil
3
BTW, la convention pour les instances est de minuscules la première lettre, il MasterInstancedevrait donc l' être masterInstance.
khoomeister
Et comment conserver la capacité de vérifier si: masterInstance instanceof Master?
jayarjo
3
util.inheritsfait une mauvaise chose en injectant la super_propriété dans l' Masterobjet. C'est inutile et essaie de traiter l'héritage prototypique comme l'héritage classique. Jetez un œil au bas de cette page pour obtenir des explications.
klh
2
@loretoparisi juste Master.prototype = EventEmitter.prototype;. Pas besoin de supers. Vous pouvez également utiliser les extensions ES6 (et c'est recommandé dans la documentation Node.js util.inherits) comme ceci class Master extends EventEmitter- vous obtenez du classique super(), mais sans rien y injecter Master.
klh
81

Héritage de classe de style ES6

La documentation Node recommande maintenant d' utiliser l'héritage de classe pour créer votre propre émetteur d'événements:

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

Remarque: Si vous définissez une constructor()fonction dans MyEmitter, vous devez appeler à super()partir de celle-ci pour vous assurer que le constructeur de la classe parente est également appelé, sauf si vous avez une bonne raison de ne pas le faire.

Breedly
la source
9
Ces commentaires ne sont pas corrects et finissent par être trompeurs dans ce cas. L'appel super()n'est pas nécessaire , tant que vous n'avez pas besoin / définissez un constructeur, donc la réponse originale de Breedly (voir l'historique EDIT) était entièrement correcte. Dans ce cas, vous pouvez copier et coller ce même exemple dans le repl, supprimer entièrement le constructeur et cela fonctionnera de la même manière. C'est une syntaxe parfaitement valide.
Aurelio
39

Pour hériter d'un autre objet Javascript, EventEmitter de Node.js en particulier mais vraiment de n'importe quel objet en général, vous devez faire deux choses:

  • fournissez un constructeur pour votre objet, qui initialise complètement l'objet; dans le cas où vous héritez d'un autre objet, vous voudrez probablement déléguer une partie de ce travail d'initialisation au super constructeur.
  • fournissez un objet prototype qui sera utilisé comme [[proto]]pour les objets créés à partir de votre constructeur; dans le cas où vous héritez d'un autre objet, vous voudrez probablement utiliser une instance de l'autre objet comme prototype.

C'est plus compliqué en Javascript qu'il n'y paraît dans d'autres langues car

  • Javascript sépare le comportement des objets en «constructeur» et «prototype». Ces concepts sont destinés à être utilisés ensemble, mais peuvent être utilisés séparément.
  • Javascript est un langage très malléable et les gens l'utilisent différemment et il n'y a pas de véritable définition unique de ce que signifie «héritage».
  • Dans de nombreux cas, vous pouvez vous en sortir en faisant un sous-ensemble de ce qui est correct, et vous trouverez des tonnes d'exemples à suivre (y compris d'autres réponses à cette question SO) qui semblent bien fonctionner pour votre cas.

Pour le cas spécifique de l'EventEmitter de Node.js, voici ce qui fonctionne:

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

Faiblesses possibles:

  • Si vous utilisez set the prototype pour votre sous-classe (Master.prototype), avec ou sans utilisation util.inherits, mais n'appelez pas le super constructeur ( EventEmitter) pour les instances de votre classe, elles ne seront pas correctement initialisées.
  • Si vous appelez le super constructeur mais ne définissez pas le prototype, les méthodes EventEmitter ne fonctionneront pas sur votre objet
  • Vous pouvez essayer d'utiliser une instance initialisée de la superclasse ( new EventEmitter) au Master.prototypelieu de Masterdemander au constructeur de la sous-classe d' appeler le super constructeur EventEmitter; en fonction du comportement du constructeur de la superclasse, cela peut sembler fonctionner correctement pendant un certain temps, mais ce n'est pas la même chose (et ne fonctionnera pas pour EventEmitter).
  • Vous pouvez essayer d'utiliser le super prototype directement ( Master.prototype = EventEmitter.prototype) au lieu d'ajouter une couche d'objet supplémentaire via Object.create; cela peut sembler fonctionner correctement jusqu'à ce que quelqu'un attrape votre objet Masteret ait par inadvertance également monkeypatched EventEmitteret tous ses autres descendants. Chaque «classe» doit avoir son propre prototype.

Encore une fois: pour hériter d'EventEmitter (ou vraiment de n'importe quel objet "classe" existant), vous voulez définir un constructeur qui s'enchaîne au super constructeur et fournit un prototype dérivé du super prototype.

métamatt
la source
19

C'est ainsi que l'héritage prototypique (prototypique?) Se fait en JavaScript. De MDN :

Fait référence au prototype de l'objet, qui peut être un objet ou nul (ce qui signifie généralement que l'objet est Object.prototype, qui n'a pas de prototype). Il est parfois utilisé pour implémenter la recherche de propriétés basée sur l'héritage de prototype.

Cela fonctionne également:

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

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

Comprendre JavaScript OOP est l'un des meilleurs articles que j'ai lus récemment sur la POO dans ECMAScript 5.

Daff
la source
7
Y.prototype = new X();est un anti-motif, veuillez utiliserY.prototype = Object.create(X.prototype);
Raynos
C'est bon à savoir. Puis-je lire plus quelque part? Serait intéressé de savoir comment les objets résultants sont différents.
Daff
4
new X()instancie une instance de X.prototypeet l'initialise en l'invoquant X. Object.create(X.prototype)instancie simplement une instance. Vous ne souhaitez Emitter.prototypepas être initialisé. Je ne trouve pas un bon article qui explique cela.
Raynos
C'est logique. Merci de l'avoir signalé. J'essaie toujours de prendre de bonnes habitudes sur Node. Les navigateurs ne sont tout simplement pas là pour ECMA5 (et si j'ai bien compris, la cale n'est pas la plus fiable).
Daff du
1
L'autre lien est également rompu. Essayez celui-ci: robotlolita.github.io/2011/10/09/…
jlukanta
5

Je pensais que cette approche de http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm était assez soignée:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

Douglas Crockford a également des modèles d'héritage intéressants: http://www.crockford.com/javascript/inheritance.html

Je trouve que l'héritage est moins souvent nécessaire dans JavaScript et Node.js. Mais en écrivant une application où l'héritage pourrait affecter l'évolutivité, je considérerais les performances mises en balance avec la maintenabilité. Sinon, je baserais ma décision uniquement sur les modèles qui conduisent à de meilleures conceptions globales, sont plus faciles à entretenir et moins sujets aux erreurs.

Testez différents modèles dans jsPerf, en utilisant Google Chrome (V8) pour obtenir une comparaison approximative. V8 est le moteur JavaScript utilisé à la fois par Node.js et Chrome.

Voici quelques jsPerfs pour vous aider à démarrer:

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

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

http://jsperf.com/inheritance-perf

wprl
la source
1
J'ai essayé cette approche et les deux emitet onsont à venir comme indéfinies.
dopatraman
N'est-ce pas le retour (ceci); juste pour enchaîner?
blablabla
1

Pour ajouter à la réponse de wprl. Il a raté la partie "prototype":

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part
guatedude2
la source
1
En fait, vous devriez utiliser Object.create au lieu de new, sinon vous obtenez l'état de l'instance ainsi que le comportement sur le prototype comme expliqué ailleurs . Mais il vaut mieux utiliser ES6 et transpile ou util.inheritscar beaucoup de gens intelligents garderont ces options à jour pour vous.
Cool Blue