使用 RequireJS,如何传入全局对象或单例?

IT技术 javascript singleton requirejs
2021-03-02 17:31:15

假设我正在主页面级别编写代码,并且 2 个依赖项需要对象的相同实例,并将其声明为依赖项。解决此问题的适当方法是什么?

基本上我想做的是说,“如果这个依赖项没有加载......然后加载它。否则,使用已经加载的相同实例并传递那个。”

6个回答

您可以将其设为module级变量。例如,

// In foo.js
define(function () {
    var theFoo = {};

    return {
        getTheFoo: function () { return theFoo; }
    };
});

// In bar.js
define(["./foo"], function (foo) {
    var theFoo = foo.getTheFoo(); // save in convenience variable

    return {
        setBarOnFoo: function () { theFoo.bar = "hello"; }
    };
}

// In baz.js
define(["./foo"], function (foo) {
    // Or use directly.
    return {
        setBazOnFoo: function () { foo.getTheFoo().baz = "goodbye"; }
    };
}

// In any other file
define(["./foo", "./bar", "./baz"], function (foo, bar, baz) {
    bar.setBarOnFoo();
    baz.setBazOnFoo();

    assert(foo.getTheFoo().bar === "hello");
    assert(foo.getTheFoo().baz === "goodbye");
};
@Raynos 听起来您对 RequireJS 的module系统和 JavaScript 的 expando-property 性质有问题,而不是我在这里提供的特定解决方案。OP 特别请求bar并且baz两者都可以访问“对象的同一实例”。
2021-04-30 17:31:15
那就是在没有全局变量的情况下实现全局变量。现在你可以在foo任何你想要的地方写入全局对象。
2021-05-03 17:31:15
我想这就是我想要的。我不想操纵 foo 的状态,但没关系。我会玩这个想法,让你知道它是如何工作的。我需要这个单例的原因是因为我有一个消息服务,它有 notify() 和 listen() 方法……但是两个不同的依赖项需要访问同一个消息服务,这样它们就可以在没有硬连线依赖项的情况下相互交谈. 我会回来报告;)
2021-05-04 17:31:15
此外,它不会污染全局命名空间。它仅适用于那些明确声明foo为依赖项的module
2021-05-19 17:31:15
@雷诺斯:假;它实现了单例模式,这是面向对象编程的方法,用于解决全局变量在过程语言中解决的问题。
2021-05-20 17:31:15

只需像您一样为您的单身人士提供一个 API。

并确保其延迟加载。最简单的方法是使用像 underscore 这样提供跨浏览器助手的抽象库。其他选项是 ES5 Object.defineProperty 或自定义 getter/setter。

在这种情况下,_.once确保构造函数的结果在第一次调用后被缓存,它基本上是延迟加载它。

define(function() {
    var constructor = _.once(function() { 
        ...
    });

    return {
        doStuffWithSingleton: function() {
            constructor().doStuff();
        }
    };

});

_.once 从下划线。

@Domenic 如果您使用_.once. _.once如果您不能使用 ES5,我实际上推荐或您自己的延迟加载 getter。
2021-04-22 17:31:15
为了制作单例而使用整个库似乎过度。接受的答案也不完全是单身人士 - 只是一种可变工厂。一个简单的 JS 单例示例在这里:dofactory.com/javascript/singleton-design-pattern
2021-04-26 17:31:15
优秀的 :)。所以现在我认为我们已经涵盖了一些内容……您的回答提供了更好的最佳实践解决方案,而我的回答涵盖了 OP 对“对象的同一实例”的特定(如果可能是误导)请求。
2021-05-01 17:31:15
我明白你的意思了。但是你仍然可以foo.lazyloaded.bar = "not me"bazmodule中使用构造函数而不是theFoo = {}真正解决任何问题,当然,如果 OP 确实需要以这种方式构造的对象,它会更强大。
2021-05-02 17:31:15
@Domenic 我意识到这是一个问题。所以我改变了我的答案,实际上并不允许访问延迟加载的对象,因为这使它变得过于公开。
2021-05-06 17:31:15

将 Raynos 对封装的担忧与 OP 的澄清结合起来,他想在消息传递服务上公开几个方法,这是我认为正确的方法:

// In messagingServiceSingleton.js
define(function () {
    var messagingService = new MessagingService();

    return {
        notify: messagingService.listen.bind(messagingService),
        listen: messagingService.notify.bind(messagingService)
    };
});

// In bar.js
define(["./messagingServiceSingleton"], function (messagingServiceSingleton) {
    messagingServiceSingleton.listen(/* whatever */);
}

// In baz.js
define(["./messagingServiceSingleton"], function (messagingServiceSingleton) {
    messagingServiceSingleton.notify(/* whatever */);
}

Function.prototype.bind不会出现在所有浏览器中,因此您需要像Mozilla 提供的那样包含一个 polyfill

另一种(在我看来可能更好)的方法是使消息服务对象本身成为一个module。这看起来像

// In messagingService.js
define(function () {
    var listenerMap = {};

    function listen(/* params */) {
        // Modify listenerMap as appropriate according to params.
    }
    function notify(/* params */) {
        // Use listenerMap as appropriate according to params.
    }

    return {
        notify: notify
        listen: listen
    };
});

由于您向使用您的module的每个人公开相同的notifylisten方法,并且这些方法始终引用相同的私有 listenerMap变量,因此这应该可以满足您的需求。它还消除了对 的需求Function.prototype.bind,并消除了消息服务本身和强制单例使用它的module之间相当不必要的区别。

有任何理由喜欢obj.f.bind(obj)vs function() { obj.f(); }像 { _.bindAll(obj)]( documentcloud.github.com/underscore/#bindAll )这样的抽象也非常适合这些情况。
2021-05-01 17:31:15
@Raynos:没有什么很好的理由,除了如果您使用它的本机版本,那么在调试时中间函数将不会出现在堆栈跟踪中。我想它可能会稍微快一点,但这当然只是微优化。
2021-05-02 17:31:15
在messagesServiceSingleton.js 中,你有'define(function () {' 但是{它引用的MessagingService 在哪里?} 因为http//google.com/search?q=MessagingService+Javascript 没有找到,所以不应该定义begin ' define(["./messagingService"], function (MessagingService) {' 以引用您的自定义消息服务.js?如果需要更正,请做。
2021-05-05 17:31:15

这是一个module本身是共享变量而不是该module内的变量的版本。

define('foo', [], {bar: "this text will be overwritten"});

define('bar', ["foo"], function (foo) {
    return {
        setBarOnFoo: function () { foo.bar = "hello"; }
    };
});

define('baz', ["foo"], function (foo) {
    return {
        setBazOnFoo: function () { foo.baz = "goodbye"; }
    };
});

require(["foo", "bar", "baz"], function (foo, bar, baz) {
    bar.setBarOnFoo();
    baz.setBazOnFoo();

    $('#results').append(foo.bar + ' ' + foo.baz);
});​​​

// reads: hello goodbye

作为 Domenic 答案的变体,您可以使用“exports”魔法module自动生成module的引用——“添加到导出对象的属性将位于module的公共接口上,无需返回任何值。 ” 这避免了必须调用getTheFoo()函数来获取引用。

// In foo.js
define(['exports'], function (foo) {
   foo.thereCanBeOnlyOne = true; 
});

// In bar.js
define(["exports", "./foo"], function (bar, foo) {
  bar.setBarOnFoo = function () { foo.bar = "hello"; };
});

// in baz.js
define(["exports", "./foo"], function (baz, foo) {
  baz.setBazOnFoo = function () { foo.baz = "goodbye"; };
});

// In any other file
define(["./foo", "./bar", "./baz"], function (foo, bar, baz) {
  bar.setBarOnFoo();
  baz.setBazOnFoo();

  assert(foo.bar === "hello");
  assert(foo.baz === "goodbye");
  assert(foo.thereCanBeOnlyeOne);
});

为了解决下面的评论,我个人发现上述约定很有用。您的里程可能会有所不同,但如果您认为它有用,请随时采用该约定。该公约归结为以下两条规则:

  • 将“exports”声明为定义数组中的第一个依赖项。
  • 在 JavaScript 文件之后命名函数中的参数。

使用文件名,例如将 foo.js 命名为变量 'foo',可以提高代码的可读性,因为大多数开发人员将定义 'foo' 作为 foo.js 依赖项的参数。扫描代码或使用 grep 时,很容易找到module内部和外部对“foo”使用的所有引用,并且可以轻松挑选出module向公众公开的内容。例如,重命名bar.setBarOnFoobar.setFooBar是容易得多,如果在bar.js声明module镜子在其他文件中使用。在所有文件中简单地搜索和替换 bar.setBarOnFoo 到 bar.setFooBar 即可完成任务。

-(我的上一个 cmt)(4)我读了你的反驳:恕我直言,这个想法很可爱,但我仍然不买,因为(a)复制了很容易改变的{东西:文件名},(b)你的全局重命名技巧很可爱,但{通常是一个大操作,因此在module def 中进行搜索替换并不昂贵}和{module使用有时不匹配其文件名,出于好的原因}和{处理这些问题&更多,我发明&做:每个非本地名称都有后缀'_ '+{一个全局唯一的永久短后缀,特别是 KCGUID},并且自动完成使打字变得容易,因此即使在所有用途的不完整重命名之后也不会发生冲突和 esp 自动查找所有用途}。
2021-05-06 17:31:15
如果您使用该exportsmodule,您真的应该exports在匿名函数的参数中给它命名吗?为什么?惯例。99.998...% 使用该exports机制的代码遵循约定。反对它会使您的代码更难阅读。实际上,该exportsmodule的存在仅仅是为了支持约定:即 CommonJS module习惯用法。exports除非您想使用此习语,否则没有令人信服的使用理由,您不会在示例代码中使用它。
2021-05-14 17:31:15
关于你的'Declare 'exports' 作为定义数组中的第一个依赖项。[然后] 在 JavaScript 文件之后命名函数中的参数':(1)对于'文件'并不意味着'module',因为后者可以被捆绑,(2)不期望也没有看到这也让我感到困惑,现在阅读你的代码,所以我 +1 @Louis cmt,包括 (3) 如果在任何地方违反约定,(聪明和赞赏) 约定是,接近理想的 {right-before --not yet done here},提醒打破为“警报:我将module名称使用的“导出”命名为理想情况下带有完整解释的链接,(4)......(我的下一条评论)
2021-05-20 17:31:15