Node.js - 继承 EventEmitter

98

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

Master.prototype.__proto__ = EventEmitter.prototype;

(来源 这里)

有人可以举个例子解释一下为什么这是一个常见的模式,以及在什么情况下它是有用的吗?


请参考此问题以获取信息:https://dev59.com/uW035IYBdhLWcg3weP7f - Juicy Scripter
14
注意:__proto__ 是一种反模式,请使用 Master.prototype = Object.create(EventEmitter.prototype); - Raynos
71
实际上,使用util.inherits(Master, EventEmitter); - thesmart
1
@Raynos 什么是反模式? - starbeamrainbowlabs
1
现在使用ES6类构造函数更加容易了。在此处检查兼容性:https://kangax.github.io/compat-table/es6/。请查看文档或我的下面的答案。 - Breedly
@Breedly是正确的。文档已经不鼓励使用util.inherits,推荐的方式是使用ES6类。util.inherits存在缺陷:https://github.com/nodejs/node/issues/4179 - Andre Pena
6个回答

96

如代码上方的注释所述,它将使MasterEventEmitter.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


5
只是一个小提醒,需要先执行 require('events').EventEmitter -- 我总是忘记。以下是文档链接,以防其他人需要:http://nodejs.org/api/events.html#events_class_events_eventemitter - mikermcneil
3
顺便提一下,实例的约定是将第一个字母小写,因此 MasterInstance 应改为 masterInstance - Chris
你如何保留检查 masterInstance 是否为 Master 的能力? - jayarjo
3
util.inherits 这个方法会将 super_ 属性注入到 Master 对象中,这是一种不必要的做法,试图将原型继承看作经典继承。请参考 页面底部的解释。 - klh
2
@loretoparisi 只需使用 Master.prototype = EventEmitter.prototype;,无需使用 supers。您也可以像这样使用 ES6 扩展(并且在 Node.js 文档的 util.inherits 中鼓励使用):class Master extends EventEmitter - 您将获得经典的 super(),但不会将任何内容注入到 Master 中。 - klh
显示剩余6条评论

90

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

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


11
这些评论是不正确的并且容易误导读者。只要你不需要/定义一个构造函数,就不需要调用super(),因此Breedly最初的答案(见EDIT历史记录)是完全正确的。在这种情况下,你可以将这个例子复制粘贴到repl中,完全删除构造函数,它仍然可以工作。这是完全有效的语法。 - Aurelio

41

要从另一个Javascript对象(特别是Node.js的EventEmitter,但实际上可以是任何对象)继承,你需要做两件事:

  • 为你的对象提供一个构造函数,它完全初始化该对象;在你从其他对象继承时,你可能想将一些这种初始化工作委托给超级构造函数。
  • 提供一个原型对象,它将用作从你的构造函数创建的对象的[[proto]];在你从其他对象继承时,你可能想使用另一个对象的实例作为你的原型。

在Javascript中,这比在其他语言中看起来更加复杂,因为:

  • 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) 来实例化您的类,它们将无法正确初始化。
  • 如果您调用了超级构造函数但没有设置原型,EventEmitter 方法将无法在您的对象上工作。
  • 您可能会尝试将已初始化的超类实例 (new EventEmitter) 用作 Master.prototype,而不是让子类构造函数 Master 调用超级构造函数 EventEmitter;根据超类构造函数的行为,这似乎可以正常工作一段时间,但不是同一件事情 (对于 EventEmitter 也不起作用)。
  • 您可能会尝试直接使用超级原型 (Master.prototype = EventEmitter.prototype),而不是通过 Object.create 添加一个额外的对象层次结构;这看起来可能可以正常工作,直到有人猴子补丁您的对象 Master,无意中也猴子补丁了 EventEmitter 和所有其他后代。每个“类”都应该有自己的原型。

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


19
这是 JavaScript 中原型继承的实现方式。来自 MDN

proto 是指对象的原型,可以是一个对象或 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面向对象编程是我最近在ECMAScript 5中阅读的最好的关于OOP的文章之一。


7
Y.prototype = new X(); 是一种反模式,请使用 Y.prototype = Object.create(X.prototype); - Raynos
4
new X() 实例化 X.prototype 并调用 X 对其进行初始化。Object.create(X.prototype) 仅仅是实例化一个对象。你不想初始化 Emitter.prototype。我找不到一个很好的文章来解释这个问题。 - Raynos
这很有道理。谢谢你指出来。我仍在努力养成 Node 的好习惯。浏览器对 ECMA5 的支持还不够(而且据我所知,shim 不是最可靠的)。 - Daff
很酷,我想我会更加认真地尝试一下。 - Daff
1
另一个链接也失效了。请尝试这个链接:http://robotlolita.github.io/2011/10/09/understanding-javascript-oop.html - jlukanta
显示剩余4条评论

5
我觉得这篇文章中介绍的方法很棒: http://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm。该方法与事件有关,用于在Node.js中创建具有事件功能的缓存。
function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

道格拉斯·克罗克福德也有一些有趣的继承模式: http://www.crockford.com/javascript/inheritance.html 我认为在JavaScript和Node.js中,继承不太常用。但是,在编写可能影响可扩展性的应用程序时,我会考虑性能与可维护性的权衡。否则,我只会基于哪些模式能够带来更好的整体设计,更易于维护且更少出错的决策。
在jsPerf中尝试不同的模式,使用Google Chrome(V8)来进行粗略比较。 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
return(this) 不就是为了链式调用吗? - blablabla

2
补充wprl的回答,他遗漏了“prototype”这一部分:
function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

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

2
实际上,你应该使用Object.create而不是new,否则你会在原型上得到实例状态和行为,如其他地方所解释的那样。但最好使用ES6并转译或util.inherits,因为很多聪明人会为你更新这些选项。 - Cool Blue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接