Node.js - herdando de EventEmitter

89

Vejo esse padrão em algumas bibliotecas Node.js:

Master.prototype.__proto__ = EventEmitter.prototype;

(fonte aqui )

Alguém pode me explicar com um exemplo, por que esse padrão é tão comum e quando é útil?

Jeffreyveon
fonte
Consulte esta pergunta para obter informações stackoverflow.com/questions/5398487/…
Juicy Scripter
14
A nota __proto__é um anti-padrão, useMaster.prototype = Object.create(EventEmitter.prototype);
Raynos
69
Na verdade, useutil.inherits(Master, EventEmitter);
thesmart
1
@Raynos O que é um anti-padrão?
starbeamrainbowlabs 01 de
1
Isso agora é mais fácil com os construtores de classe ES6. Verifique a compatibilidade aqui: kangax.github.io/compat-table/es6 . Verifique os documentos ou minha resposta abaixo.
Breedly

Respostas:

84

Como diz o comentário acima daquele código, ele fará Masterherdar de EventEmitter.prototype, então você pode usar instâncias dessa 'classe' para emitir e ouvir eventos.

Por exemplo, agora você pode fazer:

masterInstance = new Master();

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

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

Atualização : como muitos usuários apontaram, a maneira 'padrão' de fazer isso no Node seria usar 'util.inherits':

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

2ª atualização : com as aulas ES6 disponíveis, é recomendado estender a EventEmitteraula agora:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

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

myEmitter.emit('event');

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

alessioalex
fonte
3
(apenas um pequeno lembrete para fazer require('events').EventEmitterprimeiro - eu sempre esqueço. Aqui está o link para os documentos, caso alguém mais precise: nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil
3
BTW, a convenção para instâncias é minúsculas a primeira letra, então MasterInstancedeveria ser masterInstance.
khoomeister
E como você mantém a capacidade de verificar se: masterInstance instanceof Master?
Jayarjo
3
util.inheritsfaz uma coisa desagradável ao injetar a super_propriedade no Masterobjeto. É desnecessário e tenta tratar a herança prototípica como herança clássica. Dê uma olhada no final desta página para obter uma explicação.
klh
2
@loretoparisi just Master.prototype = EventEmitter.prototype;. Não há necessidade de supers. Você também pode usar extensões ES6 (e isso é incentivado na documentação do Node.js util.inherits) assim class Master extends EventEmitter- você obtém o clássico super(), mas sem injetar nada em Master.
klh
81

Herança de classe de estilo ES6

Os documentos do Node agora recomendam o uso de herança de classe para fazer seu próprio emissor de evento:

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

Observação: se você definir uma constructor()função em MyEmitter, deverá chamá super()-la a partir dela para garantir que o construtor da classe pai também seja chamado, a menos que tenha um bom motivo para não fazê-lo.

Raça
fonte
9
Esses comentários não são corretos e acabam sendo enganosos neste caso. A chamada nãosuper() é necessária , contanto que você não precise / defina um construtor, portanto, a resposta original de Breedly (consulte o histórico de EDITAR) foi totalmente correta. Neste caso, você pode copiar e colar esse mesmo exemplo no repl, remover o construtor inteiramente e tudo funcionará da mesma maneira. Essa é uma sintaxe perfeitamente válida.
Nobita de
39

Para herdar de outro objeto Javascript, o EventEmitter do Node.js em particular, mas realmente qualquer objeto em geral, você precisa fazer duas coisas:

  • fornecer um construtor para seu objeto, que inicializa completamente o objeto; no caso de você estar herdando de algum outro objeto, provavelmente desejará delegar parte desse trabalho de inicialização ao superconstrutor.
  • fornecer um objeto de protótipo que será usado como o [[proto]]para objetos criados a partir de seu construtor; no caso de você estar herdando de algum outro objeto, provavelmente deseja usar uma instância do outro objeto como seu protótipo.

Isso é mais complicado em Javascript do que pode parecer em outras linguagens porque

  • Javascript separa o comportamento do objeto em "construtor" e "protótipo". Esses conceitos devem ser usados ​​juntos, mas podem ser usados ​​separadamente.
  • Javascript é uma linguagem muito maleável e as pessoas a usam de maneira diferente e não há uma definição única e verdadeira do que significa "herança".
  • Em muitos casos, você pode se safar fazendo um subconjunto do que é correto e encontrará toneladas de exemplos a seguir (incluindo algumas outras respostas a esta pergunta do SO) que parecem funcionar bem para o seu caso.

Para o caso específico do EventEmitter do Node.js, aqui está o 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);

Possíveis pontos fracos:

  • Se você usar set o protótipo para sua subclasse (Master.prototype), com ou sem usar util.inherits, mas não chamar o super construtor ( EventEmitter) para instâncias de sua classe, eles não serão inicializados corretamente.
  • Se você chamar o super construtor, mas não definir o protótipo, os métodos EventEmitter não funcionarão em seu objeto
  • Você pode tentar usar uma instância inicializada da superclasse ( new EventEmitter) em Master.prototypevez de fazer o construtor da subclasse Masterchamar o superconstrutor EventEmitter; dependendo do comportamento do construtor da superclasse, pode parecer que está funcionando bem por um tempo, mas não é a mesma coisa (e não funcionará para EventEmitter).
  • Você pode tentar usar o superprotótipo diretamente ( Master.prototype = EventEmitter.prototype) em vez de adicionar uma camada adicional de objeto via Object.create; pode parecer que está funcionando bem até que alguém faça um monkeypatch em seu objeto Mastere inadvertidamente também faça um monkeypatch EventEmittere todos os seus outros descendentes. Cada "classe" deve ter seu próprio protótipo.

Novamente: para herdar de EventEmitter (ou realmente qualquer "classe" de objeto existente), você deseja definir um construtor que se encadeia ao superconstrutor e fornece um protótipo que é derivado do superprotótipo.

metamatt
fonte
19

É assim que a herança prototípica (prototípica?) É feita em JavaScript. Do MDN :

Refere-se ao protótipo do objeto, que pode ser um objeto ou nulo (o que geralmente significa que o objeto é Object.prototype, que não possui protótipo). Às vezes, é usado para implementar pesquisa de propriedade baseada em herança de protótipo.

Isso também funciona:

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

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

Noções básicas sobre JavaScript OOP é um dos melhores artigos que li recentemente sobre OOP no ECMAScript 5.

Daff
fonte
7
Y.prototype = new X();é um Y.prototype = Object.create(X.prototype);
antipadrão
É bom saber disso. Posso ler mais em algum lugar? Estaria interessado em como os objetos resultantes são diferentes.
Daff
4
new X()instanciar uma instância de X.prototypee inicializá-la invocando X-a. Object.create(X.prototype)apenas instancia uma instância. Você não quer Emitter.prototypeser inicializado. Não consigo encontrar um bom artigo que explica isso.
Raynos de
Isso faz sentido. Obrigado por apontar isso. Ainda estou tentando adquirir bons hábitos no Node. Os navegadores simplesmente não existem para o ECMA5 (e, pelo que entendi, o shim não é o mais confiável).
Daff de
1
O outro link também está quebrado. Experimente este: robotlolita.github.io/2011/10/09/…
jlukanta
5

Achei que essa abordagem de http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm era muito legal:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

Douglas Crockford também tem alguns padrões de herança interessantes: http://www.crockford.com/javascript/inheritance.html

Acho que a herança é menos frequentemente necessária em JavaScript e Node.js. Mas ao escrever um aplicativo em que a herança pode afetar a escalabilidade, eu consideraria o desempenho avaliado em relação à capacidade de manutenção. Caso contrário, eu apenas basearia minha decisão em quais padrões levam a melhores designs gerais, são mais fáceis de manter e menos sujeitos a erros.

Teste padrões diferentes em jsPerf, usando o Google Chrome (V8) para obter uma comparação aproximada. V8 é o mecanismo JavaScript usado tanto pelo Node.js quanto pelo Chrome.

Aqui estão alguns jsPerfs para você começar:

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

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

http://jsperf.com/inheritance-perf

wprl
fonte
1
Eu tentei essa abordagem e ambas emite onestão aparecendo como indefinidas.
dopatraman
Não é o retorno (isso); apenas para encadear?
blablabla
1

Para adicionar à resposta do wprl. Ele perdeu a parte do "protótipo":

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part
guatedude2
fonte
1
Na verdade, você deve usar Object.create em vez de new, caso contrário, você obterá o estado da instância, bem como o comportamento no protótipo, conforme explicado em outro lugar . Mas é melhor usar ES6 e transpilar ou util.inheritsjá que muitas pessoas inteligentes manterão essas opções atualizadas para você.
Cool Blue de