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方法
这是网上流传已久的例子,我想构造函数中的那一句代码是必须的吗?有何作用?我发现我注掉那一行程序也是可以正常运行的~
当然,让我们来详细探讨一下这个问题。
问题背景
在 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);
这一行,会发生什么呢?
分析
-
util.inherits
方法的作用:util.inherits
实际上是一个简单的辅助方法,用于设置原型链。它会将EventEmitter.prototype
设置为MyStream.prototype
的原型,从而让MyStream
的实例能够访问EventEmitter
的方法。
-
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)
仍然是推荐的做法,因为它确保了构造函数内的初始化逻辑被正确执行。