web-dev-qa-db-fra.com

À l'aide de RequireJS, comment puis-je passer des objets globaux ou des singletons?

Disons que j'écris du code au niveau de la page principale et que 2 dépendances nécessitent la même instance d'un objet et indiquent également cela comme une dépendance. Quelle est la meilleure façon de procéder?

Fondamentalement, ce que je veux faire est de dire: "Si cette dépendance n'est pas chargée ... alors chargez-la. Sinon, utilisez la même instance qui était déjà chargée et passez-la."

54
egervari

Vous en feriez une variable de niveau module. Par exemple,

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

Fournissez simplement une API pour votre singleton comme vous le feriez.

Et assurez-vous qu'il est paresseusement chargé. Le moyen le plus simple consiste à utiliser une bibliothèque d'abstraction comme le soulignement qui fournit des aides multi-navigateurs. Les autres options sont ES5 Object.defineProperty ou getter/setters personnalisés.

Dans ce cas _.once garantit que le résultat du constructeur est mis en cache après le premier appel, il le charge paresseusement.

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

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

});

_.once du trait de soulignement.

8
Raynos

En combinant les préoccupations de Raynos concernant l'encapsulation avec la clarification du PO selon laquelle il souhaite exposer quelques méthodes sur un service de messagerie, c'est à mon avis la bonne façon de procéder:

// 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 ne sera pas présent dans tous les navigateurs, vous devrez donc inclure un polyfill comme celui fourni par Mozilla .

Une autre approche (et à mon avis probablement meilleure) serait de faire de l'objet de service de messagerie lui-même un module. Cela ressemblerait à quelque chose comme

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

Puisque vous exposez les mêmes méthodes notify et listen à tous ceux qui utilisent votre module, et celles-ci font toujours référence à la même variable privatelistenerMap, cette devrait faire ce que vous voulez. Cela évite également le besoin de Function.prototype.bind, et supprime la distinction plutôt inutile entre le service de messagerie lui-même et le module qui en impose l'utilisation singleton.

6
Domenic

Voici une version où le module lui-même est la variable partagée au lieu d'une variable dans ce 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
1
Benny Bottema

Comme variante de la réponse de Domenic, vous pouvez utiliser le module magique 'exports' pour générer automatiquement une référence pour le module - "Les propriétés ajoutées à l'objet exports seront sur l'interface publique du module, pas besoin de retourner une valeur. " Cela évite d'avoir à appeler la fonction getTheFoo() pour obtenir une référence.

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

Pour répondre au commentaire ci-dessous, j'ai personnellement trouvé la convention ci-dessus utile. Votre kilométrage peut varier, mais n'hésitez pas à adopter la convention si vous pensez qu'elle est utile. La convention se résume à ces deux règles:

  • Déclarez les "exportations" comme première dépendance dans le tableau define.
  • Nommez le paramètre dans la fonction après le fichier JavaScript.

Utiliser le nom du fichier, par ex. pour foo.js, nommez la variable 'foo', augmente la lisibilité du code car la plupart des développeurs définiront 'foo' comme paramètre pour la dépendance foo.js. Lors de la numérisation du code ou de l'utilisation de grep, il est facile de trouver toutes les références à "foo" à utiliser à l'intérieur et à l'extérieur du module et il est facile de choisir ce que le module expose au public. Par exemple, renommer bar.setBarOnFoo à bar.setFooBar est beaucoup plus facile si la déclaration du module bar.js reflète l'utilisation dans d'autres fichiers. Une simple recherche et remplacement de bar.setBarOnFoo par bar.setFooBar dans tous les fichiers accomplira la tâche.

0
Eric Bronnimann