Node.js-从EventEmitter继承


89

我在许多Node.js库中看到了这种模式:

Master.prototype.__proto__ = EventEmitter.prototype;

(资源 在这里

有人可以举一个例子给我解释一下,为什么这是一种常见的模式,什么时候方便?



14
请注意,这__proto__是一种反模式,请使用Master.prototype = Object.create(EventEmitter.prototype);
Raynos 2012年

69
实际使用util.inherits(Master, EventEmitter);
thesmart 2012年

1
@Raynos什么是反模式?
starbeamrainbowlabs

1
现在,使用ES6类构造函数更容易。在此处检查compat:kangax.github.io/compat-table/es6。检查下面的文档或我的答案。
2016年

Answers:


84

就像该代码上面的注释所言,它将Master继承自EventEmitter.prototype,因此您可以使用该“类”的实例来发出和侦听事件。

例如,您现在可以执行以下操作:

masterInstance = new Master();

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

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

更新:正如许多用户指出的那样,在Node中执行此操作的“标准”方法是使用“ util.inherits”:

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

第二次更新:我们现在有了ES6类,建议EventEmitter现在扩展该类:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

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

myEmitter.emit('event');

请参阅https://nodejs.org/api/events.html#events_events


4
(首先提醒require('events').EventEmitter一下,我总是忘记了。以下是指向文档的链接,以防万一其他人需要它:nodejs.org/api/events.html#events_class_events_eventemitter
mikermcneil 2014年

3
顺便说一句,实例的惯例是小写第一个字母,所以MasterInstance应该是masterInstance
khoomeister

以及如何保持检查以下各项的能力:masterInstance instanceof Master?
jayarjo 2014年

3
util.inheritssuper_属性注入Master对象确实很讨厌。这是不必要的,并尝试将原型继承像经典继承一样对待。请看页面底部的说明。
klh 2015年

2
@loretoparisi只是Master.prototype = EventEmitter.prototype;。不需要超级。您还可以util.inherits像这样使用ES6扩展(在Node.js文档中对此有所鼓励)class Master extends EventEmitter-您会获得经典的体验super(),但无需向注入任何东西Master
klh

81

ES6样式类继承

现在,Node文档建议使用类继承来创建自己的事件发射器:

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

注意:如果在中定义constructor()函数MyEmitter,则应super()从中调用以确保也调用了父类的构造函数,除非有充分的理由不这样做。


9
这些评论是不正确的,在这种情况下最终会产生误导。调用super()不需要的,只要你并不需要/定义构造函数,因此Breedly的原来的答复(见编辑历史)是完全正确的。在这种情况下,您可以将这个完全相同的示例复制并粘贴到repl中,完全删除构造函数,其工作方式将相同。那是完全有效的语法。
Aurelio'7

39

要从另一个Javascript对象(尤其是Node.js的EventEmitter,但实际上实际上是任何对象)继承,您需要做两件事:

  • 为您的对象提供一个构造函数,该构造函数完全初始化该对象;如果要从其他对象继承,则可能要将某些初始化工作委托给超级构造函数。
  • 提供一个原型对象,该对象将用作[[proto]]从构造函数创建的for对象;如果要从其他对象继承,则可能要使用其他对象的实例作为原型。

与其他语言相比,它在Java语言中的处理更为复杂,因为

  • Javascript将对象行为分为“构造函数”和“原型”。这些概念旨在一起使用,但可以单独使用。
  • Javascript是一种非常具有延展性的语言,人们使用它的方式有所不同,对于“继承”的含义没有唯一的真实定义。
  • 在许多情况下,您可以避免做一些正确的事情,而您会发现大量可以效仿的例子(包括对该SO问题的其他答案)。

对于Node.js的EventEmitter的特定情况,这是可行的:

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

可能的缺点:

  • 如果使用或不使用来为子类(Master.prototype)设置原型util.inherits,但是不EventEmitter为类的实例调用super构造函数(),则不会正确初始化它们。
  • 如果您调用超级构造函数但未设置原型,则EventEmitter方法将不适用于您的对象
  • 您可能会尝试使用superclass(new EventEmitter)的初始化实例,Master.prototype而不是让子类构造函数Master调用super构造函数EventEmitter;取决于超类构造函数的行为,它似乎可以在一段时间内正常工作,但不是一回事(并且不适用于EventEmitter)。
  • 您可以尝试直接使用超级原型(Master.prototype = EventEmitter.prototype),而不是通过Object.create添加额外的对象层;直到有人用猴子修补您的对象Master并且无意间也用猴子修补EventEmitter了其他所有后代,这似乎还不错。每个“类”都应该有自己的原型。

再次:要继承自EventEmitter(或实际上是任何现有的对象“类”),您需要定义一个构造函数,该构造函数链接到超级构造函数,并提供从超级原型派生的原型。


19

这就是在JavaScript中完成原型(原型?)继承的方式。从MDN

指对象的原型,可以是一个对象或为null(通常意味着该对象是Object.prototype,没有原型)。有时用于实现基于原型继承的属性查找。

这也可以:

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

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

了解JavaScript OOP是我最近在ECMAScript 5中有关OOP的最佳文章之一。


7
Y.prototype = new X();是反模式,请使用Y.prototype = Object.create(X.prototype);
Raynos 2012年

很高兴知道。我可以在某处阅读更多吗?会感兴趣的是结果对象如何不同。
Daff 2012年

4
new X()实例化实例X.prototype并通过对其进行调用X对其进行初始化。Object.create(X.prototype)只是实例化一个实例。您不需Emitter.prototype初始化。我找不到一篇很好的文章来解释这一点。
雷诺斯

这很有道理。感谢您指出。仍然尝试在Node上养成良好的习惯。ECMA5并不提供浏览器(据我所知,shim不是最可靠的)。
Daff 2012年

1
另一个链接也断开。:试试这个robotlolita.github.io/2011/10/09/...
jlukanta

5

我认为http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm中的这种方法非常简洁:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

道格拉斯·克罗克福德(Douglas Crockford)也有一些有趣的继承模式:http : //www.crockford.com/javascript/inheritance.html

我发现在JavaScript和Node.js中,继承的需求较少。但是在编写继承可能影响可扩展性的应用程序时,我会考虑性能与可维护性之间的权衡。否则,我将仅基于哪种模式可导致更好的总体设计,更可维护且更不易出错的决策。

使用Google Chrome(V8)在jsPerf中测试不同的模式,以进行粗略的比较。V8是Node.js和Chrome都使用的JavaScript引擎。

这是一些jsPerfs可以帮助您入门:

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

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

http://jsperf.com/inheritance-perf


1
我试过这个方法无一不emiton涌现出来的不确定。
dopatraman 2014年

这不是回报吗?只是为了链接?
blablabla 2015年

1

添加到wprl的响应中。他错过了“原型”部分:

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part

1
实际上,您应该使用Object.create而不是new,否则您将获得实例状态以及原型上的行为,如其他地方所述。但是最好使用ES6进行转换,或者util.inherits因为许多聪明的人会为您更新这些选项。
酷蓝
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.