使用RequireJS,我如何传递全局对象或单例

Using RequireJS, how do I pass in global objects or singletons around?

本文关键字:全局 对象 单例 何传递 RequireJS 使用      更新时间:2023-09-26

假设我在主页级编写代码,两个依赖项需要一个对象的相同实例,并将其声明为依赖项。做这件事的合适方法是什么?

基本上我想做的是说,"如果这个依赖没有加载…然后载入。否则,使用已经加载的同一个实例,并只传递那个实例。"

您可以将其设置为模块级变量。例如,

// 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");
};

只要为你的单例提供一个API就可以了。

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

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

define(function() {
    var constructor = _.once(function() { 
        ...
    });
    return {
        doStuffWithSingleton: function() {
            constructor().doStuff();
        }
    };
});

_.once from下划线

结合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。

另一种方法(在我看来可能更好)是将消息传递服务对象本身作为模块。它看起来像

// 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
    };
});

由于您将相同的notifylisten方法暴露给使用您的模块的每个人,并且这些方法总是引用相同的私有listenerMap变量,因此这应该可以满足您的要求。它还消除了对Function.prototype.bind的需求,并消除了消息传递服务本身与强制其单例使用的模块之间相当不必要的区别。

在这个版本中,模块本身是共享变量,而不是该模块中的变量。

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'魔法模块自动为模块生成引用——"添加到exports对象的属性将位于模块的公共接口上,无需返回任何值。"这避免了必须调用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时,很容易找到模块内外对'foo'使用的所有引用,并且很容易找出模块向公众公开的内容。例如,如果bar.js模块中的声明反映了其他文件中的用法,则将bar.setBarOnFoo重命名为bar.setFooBar要容易得多。一个简单的搜索和替换吧。将baronfoo设置为bar。

我在这种情况下:

由于不同的原因,我需要调用一个在需求模块上的函数,但是触发该调用的点击不在需求中。

我解决这个问题的方法是创建一个需求模型,在窗口对象上写。

define("one", [], function() {
    window.popupManager = (function () {
            console.log ('aca');
        var popUpManager = function () {
            self = this;
            self.CallMe = function ()
            {
                alert ('someone calls');
            };
        };
        return new popUpManager();
    })();
});
require(['one']);
window.popupManager.CallMe();

这样,如果任何一段代码超出了require的范围(我知道不应该是这样),就可以调用这个require的函数,它写在窗口对象上。

我真的知道这不是一个"优雅"的解决方案,但它可能会在紧急情况下帮助你。