Nodejs中events.EventEmitter.call(this)是必须吗?

Nodejs中events.EventEmitter.call(this)是必须吗?

function MyStream() {
//events.EventEmitter.call(this);//call方法使得MyStream对象继承了EventEmitter对象上的方法
}

util.inherits(MyStream, events.EventEmitter);//使用inherits方法

这是网上流传已久的例子,我想构造函数中的那一句代码是必须的吗?有何作用?我发现我注掉那一行程序也是可以正常运行的~


6 回复

当然,让我们来详细探讨一下这个问题。

问题背景

在 Node.js 中,events.EventEmitter 是一个非常重要的类,它提供了事件触发与监听的基础功能。如果你希望创建一个自定义的类来扩展 EventEmitter 的功能,通常需要调用 events.EventEmitter.call(this) 来确保新的类能够正确地继承 EventEmitter 的方法和属性。

示例代码

首先,我们来看一下原始代码:

const EventEmitter = require('events');
const util = require('util');

function MyStream() {
    // events.EventEmitter.call(this); // call方法使得MyStream对象继承了EventEmitter对象上的方法
}

util.inherits(MyStream, EventEmitter);

在这个例子中,util.inherits 方法被用来继承 EventEmitter 的所有方法。但是,如果我们注释掉 events.EventEmitter.call(this); 这一行,会发生什么呢?

分析

  1. util.inherits 方法的作用

    • util.inherits 实际上是一个简单的辅助方法,用于设置原型链。它会将 EventEmitter.prototype 设置为 MyStream.prototype 的原型,从而让 MyStream 的实例能够访问 EventEmitter 的方法。
  2. events.EventEmitter.call(this) 的作用

    • 当你在构造函数中调用 events.EventEmitter.call(this) 时,实际上是调用了 EventEmitter 构造函数,并且将当前实例作为上下文传递给该构造函数。这确保了 EventEmitter 构造函数中的初始化逻辑(例如事件监听器列表的初始化)会被正确执行。
    • 如果你没有调用 events.EventEmitter.call(this),那么 EventEmitter 的构造函数不会被执行,某些内部状态可能不会被正确初始化,从而导致潜在的问题。

实验验证

让我们通过实验来验证这一点:

const EventEmitter = require('events');
const util = require('util');

// 没有调用 events.EventEmitter.call(this)
function MyStream() {
    // events.EventEmitter.call(this); // 注释掉这一行
}

util.inherits(MyStream, EventEmitter);

const myStream = new MyStream();
myStream.on('data', (data) => {
    console.log(data);
});

myStream.emit('data', 'Hello World'); // 输出 "Hello World"

在这个例子中,尽管我们没有显式调用 events.EventEmitter.call(this),但代码依然可以正常运行。这是因为 util.inherits 已经设置了正确的原型链,使得 MyStream 实例可以访问 EventEmitter 的方法。

结论

虽然 events.EventEmitter.call(this) 在某些情况下不是绝对必需的(特别是在使用 util.inherits 的情况下),但它仍然是一种最佳实践。它确保了 EventEmitter 构造函数中的初始化逻辑被执行,避免了潜在的问题。因此,在编写代码时,最好保留这一行,以确保代码的健壮性和可维护性。


inherited是基与原型的继承,call是基于对象继承,可以获取对象方法

可以看一下 events 模块的源码:

https://github.com/joyent/node/blob/828f14556e0daeae7fdac08fceaa90952de63f73/lib/events.js#L43

EventEmitter 的初始化最初是没有内容的,后来引入 domain 特性时才增加了这个 init

https://github.com/joyent/node/commit/963459d736d6594de641aff4d8767da113359457#diff-4

也就是说,只要没有用到 domain 模块,就可以不用 call 这个 constructor

文档里有讲 domain 对 EventEmitter 的影响: http://nodejs.org/api/domain.html#domain_implicit_binding

谢谢,获益匪浅~

node的domain是捕获最近的错误异常的,以至于不会被冒泡到最顶部而被process.on(‘uncaughtException’)捕获,用于响应500,良好体验,domain.createDomain()的on,once,emit应该是继承与EventEmitter.prototype

events.EventEmitter.call(this) 这行代码的作用是在自定义类(例如 MyStream)中调用 EventEmitter 的构造函数,从而将 EventEmitter 的方法继承到自定义类的实例上。虽然你可以通过 util.inherits 方法来实现类似的效果,但直接调用 EventEmitter 的构造函数是一种更直接的方式。

如果你注释掉这行代码,MyStream 实例将不会直接拥有 EventEmitter 的方法。但是,由于你使用了 util.inherits 方法,MyStream 类仍然会继承 EventEmitter 的所有方法。因此,在这种情况下,注释掉这行代码并不会导致程序崩溃或出现错误,但这并不是因为 EventEmitter.call(this) 不重要,而是因为 util.inherits 已经处理了继承关系。

下面是带有和不带有 EventEmitter.call(this) 的示例代码:

带有 EventEmitter.call(this) 的代码

const EventEmitter = require('events');
const util = require('util');

function MyStream() {
    EventEmitter.call(this); // 继承 EventEmitter 的方法
}

util.inherits(MyStream, EventEmitter);

MyStream.prototype.write = function(data) {
    this.emit('data', data);
};

const stream = new MyStream();
stream.on('data', (data) => console.log(`Received: ${data}`));
stream.write('Hello World');

不带 EventEmitter.call(this) 的代码

const EventEmitter = require('events');
const util = require('util');

function MyStream() {
    // EventEmitter.call(this); // 注释掉这行代码
}

util.inherits(MyStream, EventEmitter);

MyStream.prototype.write = function(data) {
    this.emit('data', data);
};

const stream = new MyStream();
stream.on('data', (data) => console.log(`Received: ${data}`));
stream.write('Hello World'); // 这行代码可能需要调整以确保正确调用

在这两个示例中,util.inherits 确保了 MyStream 类继承了 EventEmitter 的所有方法。然而,EventEmitter.call(this) 仍然是推荐的做法,因为它确保了构造函数内的初始化逻辑被正确执行。

回到顶部