Используя RequireJS, как мне передать глобальные объекты или синглтоны? - PullRequest
54 голосов
/ 10 апреля 2011

Допустим, я пишу код на уровне главной страницы, и для 2 зависимостей требуется один и тот же экземпляр объекта, а также указывается это как зависимость. Как правильно это сделать?

По сути, я хочу сказать: «Если эта зависимость не загружена ... тогда загрузите ее. В противном случае используйте тот же экземпляр, который уже был загружен, и просто передайте его».

Ответы [ 6 ]

58 голосов
/ 10 апреля 2011

Вы бы сделали это переменной уровня модуля.Например,

// 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");
};
8 голосов
/ 10 апреля 2011

Просто предоставьте API для вашего синглтона, как вы бы.

И убедитесь, что он лениво загружен. Самый простой способ - использовать библиотеку абстракций, например, подчеркивание, которая предоставляет кросс-браузерные помощники. Другими параметрами являются ES5 Object.defineProperty или пользовательские методы получения / установки.

В этом случае _.once гарантирует, что результат конструктора будет кэширован после первого вызова, он в основном лениво загружает его.

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

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

});

_.once от подчеркивания.

6 голосов
/ 10 апреля 2011

Сочетая опасения Райноса по поводу инкапсуляции с разъяснением ОП о том, что он хочет представить несколько методов в службе обмена сообщениями, я думаю, что это правильный путь:

// 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 предоставляет .

Альтернативный (и, на мой взгляд, вероятно, лучший) подход состоит в том, чтобы сделать объект службы сообщений самим модулем. Это будет выглядеть примерно так:

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

Поскольку вы предоставляете одинаковые методы notify и listen всем, кто использует ваш модуль, и они всегда ссылаются на одну и ту же переменную private listenerMap, это должно делать то, что вы хотите. Это также устраняет необходимость в Function.prototype.bind и избавляет от довольно ненужного различия между самой службой обмена сообщениями и модулем, который обеспечивает ее использование синглтоном.

1 голос
/ 23 июля 2012

Вот версия, в которой сам модуль является общей переменной, а не переменной внутри этого модуля.

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
0 голосов
/ 03 февраля 2016

В качестве варианта ответа Доменика вы можете использовать магический модуль «экспорта» для автоматического создания ссылки для модуля - «Свойства, добавленные к объекту экспорта, будут находиться в открытом интерфейсе модуль, нет необходимости возвращать любое значение. " Это исключает необходимость вызова функции 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);
});

Чтобы ответить на комментарий ниже, я лично нашел вышеупомянутое соглашение полезным. Ваш пробег может варьироваться, но не стесняйтесь принять соглашение, если вы считаете, что это полезно Соглашение сводится к этим двум правилам:

  • Объявите 'export' как первую зависимость в массиве определения.
  • Назовите параметр в функции после файла JavaScript.

Используя имя файла, например, для foo.js имя переменной «foo» увеличивает читабельность кода, поскольку большинство разработчиков определяют «foo» в качестве параметра зависимости foo.js. При сканировании кода или использовании grep легко найти все ссылки на использование 'foo' как внутри, так и снаружи модуля, и это позволяет легко определить, что модуль представляет для публики. Например, переименование bar.setBarOnFoo в bar.setFooBar намного проще, если объявление в модуле bar.js отражает использование в других файлах. Простой поиск и замена bar.setBarOnFoo на bar.setFooBar по всем файлам выполнит задачу.

0 голосов
/ 01 февраля 2016

Я был в этом сценарии:

По разным причинам мне нужно было вызвать функцию, которая была в модуле requirejs, но щелчок, который вызвал этот вызов, был вне потребности.

Я исправил это, создав моду requirejs, которая записывает поверх объекта окна.

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();

Таким образом, если какой-либо фрагмент кода выходит за пределы требуемого спектра (я знаю, что так быть не должно)может вызывать функции этого, требующие записи поверх объекта окна.

Я действительно знаю, что это не «элегантное» решение, но оно может помочь вам в случае чрезвычайной ситуации.

...