【问题标题】:Make a function behave like an instantiated EventEmitter使函数表现得像实例化的 EventEmitter
【发布时间】:2015-04-19 02:39:35
【问题描述】:

我非常喜欢将函数导出为 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 instance 行为的可靠模式?简单地遍历一次性实例的所有属性并将它们复制到目标函数是否足够好?是否值得尝试模仿继承与非继承的设置?考虑到默认的this 将被更改,是否有任何奇怪的情况需要考虑?或者有没有更好、更明智的方法?

【问题讨论】:

  • 你可以扩展节点的事件发射器,不需要重新发明轮子。
  • 我见过的每一段“扩展”EventEmitter 的代码都被实现为一个构造函数,调用 EventEmitter 作为一个超构造函数。还是您指的是_.extend(fn, (new EventEmitter())) 之类的东西?后者让我觉得很奇怪,所以我问它是否有任何设计缺陷。
  • 我通常只添加自己的属性来在新的​​ EE 上创建方法,因为我不需要继承,因为通常由一个 EE 处理这项工作。通常,它只不过是在我关闭模块之前添加一两个新方法并调用一些 this.on() 来设置一些行为
  • 如果我想导出一个简单的对象,给它分配一个新的 EE 并添加方法会很简单,同意。但在这种情况下,我想要实现的是一个函数来代替那个新的 EE 对象。因此,似乎我别无选择,只能从一次性的新 EE 中手动复制所有内容,如果我做出太多假设,这势必容易出错或出错。
  • 我不明白为什么容器需要是一个函数,但是 fn.ee=myEE;工作,还是方法需要是函数本身的属性?如果是这样,请使用 extend()...

标签: javascript node.js eventemitter


【解决方案1】:

简单地遍历一次性实例的所有属性并将它们复制到目标函数是否足够好?

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

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

是否值得尝试模仿继承与非继承的设置?

不是真的。你坚持单例使用你的 api,所以你根本不需要任何继承。

考虑到默认的this 将被更改,是否有任何奇怪的情况需要考虑?

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

【讨论】:

    【解决方案2】:

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

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

    var OSPreferences = function(){
      // ...
    };
    
    Object.setPrototypeOf(OSPreferences, new EventEmitter());

    【讨论】:

    • 如果你希望它也能在浏览器中工作,你可以回退到 __proto__ 属性用于尚不支持 setPrototypeOf() 的浏览器。
    • 这是我担心的事情,但不确定它有多大的实际问题。根据 Bergi 的回答,EventEmitter 似乎通常不应该给我任何问题,就像目前实施的那样。但总的来说,这仍然是一个有趣的问题。我知道 setPrototypeOf 对性能有影响,你知道这方面的可靠数据吗? (它往往有多糟糕,等等)
    • @SethHolladay,我不会担心 setPrototypeOf 的性能,因为你只为你的单例调用它一次。如果我担心,我可能会担心原型链上的方法调用成本与对象本身的成本。要找到答案,我建议您创建一个 jsPerf 测试,例如 jsperf.com/scope-chain-lookup-vs-prototype-chain-lookup/3
    • 我最担心的是,尽管typeof OSPreferences == "function"OSPreferences.call()/apply/bind 这样的东西不再起作用。这会引发很多程序。
    • 对,当然。哎呀。有时候,这个世界并没有按照你想要的方式运转。哦,好吧。
    【解决方案3】:

    ES6 class syntax 是最优雅的子类化解决方案。

    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 版本不支持类语法。您必须改用iojs

    【讨论】:

    • 这对于希望他们的 API 成为构造函数的人来说看起来不错。不幸的是,这对我的用例来说会很混乱,因为osPreferences 可能是一个 getter 或 setter,具体取决于它给出的参数类型,可能是其他函数的快捷方式,并且有可能将数据保存到磁盘等。它是一个快捷方式,而不是一个类。
    • 我仍然建议您重新设计代码结构并使用类语法,而不是应用任何其他解决方案。
    猜你喜欢
    • 1970-01-01
    • 2010-09-14
    • 2013-07-25
    • 2023-03-18
    • 2020-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多