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