使函数的行为类似于实例化的 EventEmitter

Make a function behave like an instantiated EventEmitter

我非常喜欢将函数导出为 JavaScript 模块的主要 API 的模式。原因是在 JS 中,一个函数基本上可以做一个典型对象可以做的任何事情,然后再做一些。

所以这对我来说很典型:

function stuff() {}
function thing() { /* shortcut or default behavior */ }
thing.stuff = stuff;
module.exports = thing;

现在我 运行 遇到了我希望 thing 表现得像 EventEmitter 实例 的情况。我希望它成为构造函数。

为什么?好吧 thing 确实与 osPreferences 类似,其中使用一些选项调用它会将数据保存到磁盘。用户实例化它没有任何意义。 new OSPreferences() 没有多大用处,因为您的计算机一次只能遵循一组首选项。

然而,变化 可以 在我 API 之外随时发生。所以有一个巨大的好处:

osPreferences.on('change', fn);

所以问题是,什么是吸收 EventEmitter 实例 行为的可靠模式?简单地遍历一次性实例的所有属性并将它们复制到目标函数是否足够好?是否值得尝试模仿继承与非继承设置?考虑到默认 this 将被更改,是否有任何奇怪的情况需要考虑?还是有更好更明智的方法?

ES6 class syntax 是 subclassing 最优雅的解决方案。

let EventEmitter = require('events'); //or `require('events').EventEmitter` in nodejs
Class osPreferences extends EventEmitter{
    constructor(){
        super();
        console.log(this);
    }
}
let osp = new osPreferences();
osp.on('change',function(){
    console.log('osPreferences changed');
});
osp.emit('change');

很遗憾,最新的 Node 版本不支持 class 语法。您必须改用 iojs

循环遍历和复制原始对象的方法和属性可能会失败,具体取决于 EventEmitter 的实现方式。当大量使用闭包来隐藏属性时,这种方法很可能会失败。

在我看来,最好的方法是直接在函数实例上设置原型。对于您的示例,它将类似于:

var OSPreferences = function(){
  // ...
};

Object.setPrototypeOf(OSPreferences, new EventEmitter());

Is it good enough to simply loop through all properties of a throwaway instance and copy them to the target function?

是的。您甚至不需要创建一次性实例,只需将所有 EventEmitter.prototype 方法复制到您的函数中,然后将 EventEmitter 应用于它即可。

function osPreferences() { … }
for (var p in EventEmitter.prototype)
    osPreferences[p] = EventEmitter.prototype[p];
EventEmitter.call(osPreferences);

Is it worth trying to mimic the inherited vs non-inherited setup?

不是真的。您坚持单例使用 api,因此您根本不需要任何继承。

Are there any weird cases to take into account, given that the default this would be changed?

没有,EventEmitter is coded quite defensively。当然,您的听众可能会觉得不寻常……