Le monde de la programmation informatique est en fait un processus consistant à constamment abstraire des parties simples et à organiser ces abstractions. JavaScript ne fait pas exception. Lorsque nous utilisons JavaScript pour écrire des applications, utilisons-nous tous du code écrit par d'autres, comme certaines bibliothèques ou frameworks open source célèbres. À mesure que notre projet se développe, de plus en plus de modules sur lesquels nous devons nous appuyer deviennent de plus en plus importants. À l'heure actuelle, la manière d'organiser efficacement ces modules est devenue une question très importante. L'injection de dépendances résout le problème de l'organisation efficace des modules dépendants du code. Vous avez peut-être entendu le terme « injection de dépendances » dans certains frameworks ou bibliothèques, comme le célèbre framework front-end AngularJS. L'injection de dépendances est l'une des fonctionnalités très importantes. Cependant, l’injection de dépendances n’a rien de nouveau. Elle existe depuis longtemps dans d’autres langages de programmation tels que PHP. Dans le même temps, l’injection de dépendances n’est pas aussi compliquée qu’on l’imagine. Dans cet article, nous allons apprendre le concept d'injection de dépendances en JavaScript et expliquer de manière simple et conviviale comment écrire du code "style d'injection de dépendances".
Définition d'objectifs
Supposons que nous ayons maintenant deux modules. Le premier module est utilisé pour envoyer des requêtes Ajax, tandis que le deuxième module est utilisé comme routeur.
var service = fonction() {
Retourner { nom : 'Service' };
>
var routeur = fonction() {
Retourner { nom : 'Routeur' };
>
A cette époque, nous avons écrit une fonction qui nécessite l'utilisation des deux modules mentionnés ci-dessus :
var doSomething = fonction (autre) {
var s = service();
var r = routeur();
};
Ici, afin de rendre notre code plus intéressant, ce paramètre doit recevoir quelques paramètres supplémentaires. Bien sûr, nous pouvons complètement utiliser le code ci-dessus, mais le code ci-dessus est légèrement moins flexible sous tous ses aspects. Que se passe-t-il si le nom du module que nous devons utiliser change en ServiceXML ou ServiceJSON ? Ou que se passe-t-il si nous voulons utiliser de faux modules à des fins de tests. À ce stade, nous ne pouvons pas simplement modifier la fonction elle-même. Donc la première chose que nous devons faire est de passer le module dépendant en paramètre à la fonction, le code est le suivant :
var doSomething = function (service, routeur, autre) {
var s = service();
var r = routeur();
};
Dans le code ci-dessus, nous transmettons exactement les modules dont nous avons besoin. Mais cela soulève un nouveau problème. Supposons que nous appelions la méthode doSomething dans les deux parties du code. À ce stade, que se passe-t-il si nous avons besoin d'une troisième dépendance. Pour le moment, ce n’est pas une bonne idée de modifier tout le code d’appel de fonction. Par conséquent, nous avons besoin d’un morceau de code pour nous aider à le faire. C’est le problème que l’injecteur de dépendances tente de résoudre. Nous pouvons désormais fixer nos objectifs :
1. Nous devrions pouvoir enregistrer les dépendances
2. L'injecteur de dépendances doit recevoir une fonction puis renvoyer une fonction capable d'obtenir les ressources requises
3. Le code ne doit pas être compliqué, mais doit être simple et convivial
4. L'injecteur de dépendances doit conserver la portée de la fonction transmise
5. La fonction transmise doit pouvoir recevoir des paramètres personnalisés, pas seulement les dépendances décrites
méthode requirejs/AMD
Peut-être avez-vous entendu parler du fameux requirejs, qui est une bibliothèque qui peut très bien résoudre les problèmes d'injection de dépendances :
définir(['service', 'routeur'], fonction(service, routeur) {
// ...
});
L'idée de requirejs est que nous devons d'abord décrire les modules requis, puis écrire nos propres fonctions. Parmi eux, l’ordre des paramètres est important. Supposons que nous devions écrire un module appelé injecteur capable d'implémenter une syntaxe similaire.
var doSomething = injector.resolve(['service', 'router'], function(service, routeur, autre) {
Expect(service().name).to.be('Service');
Expect(router().name).to.be('Router');
Attendre(autre).to.be('Autre');
});
faireQuelquechose("Autre");
Avant de continuer, une chose qui doit être expliquée est que dans le corps de la fonction doSomething, nous utilisons la bibliothèque d'assertions expect.js pour garantir l'exactitude du code. Il y a ici quelque chose de similaire à l'idée du TDD (Test Driven Development).
Maintenant, nous commençons officiellement à écrire notre module d'injection. Tout d’abord, il doit s’agir d’un monolithe afin qu’il ait les mêmes fonctionnalités dans chaque partie de notre application.
var injecteur = {
dépendances : {},
Registre : fonction (clé, valeur) {
This.dependencies[key] = valeur;
},
Résoudre : fonction (deps, func, scope) {
}
>
Cet objet est très simple, ne contenant que deux fonctions et une variable à des fins de stockage. Ce que nous devons faire est de vérifier le tableau deps, puis de rechercher la réponse dans la variable de dépendances. La partie restante consiste à utiliser la méthode .apply pour appeler la variable func que nous avons passée :
résoudre : fonction (deps, func, scope) {
var arguments = [];
pour(var i=0; i
if(this.dependencies[d]) {
args.push(this.dependencies[d]);
} autre {
throw new Error('Impossible de résoudre ' d);
>
>
Fonction de retour() {
func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0)));
>
Si vous devez spécifier une portée, le code ci-dessus peut également s'exécuter normalement.
Dans le code ci-dessus, la fonction de Array.prototype.slice.call(arguments, 0) est de convertir la variable arguments en un véritable tableau. Jusqu’à présent, notre code réussit parfaitement le test. Mais le problème ici est que nous devons écrire les modules requis deux fois et que nous ne pouvons pas les organiser dans aucun ordre. Les paramètres supplémentaires viennent toujours après toutes les dépendances.
Méthode de réflexion
Selon l'explication de Wikipédia, la réflexion signifie qu'un objet peut modifier sa propre structure et son comportement pendant l'exécution du programme. En JavaScript, pour parler simplement, c'est la capacité de lire le code source d'un objet et d'analyser le code source. En revenant toujours à notre méthode doSomething, si vous appelez la méthode doSomething.toString(), vous pouvez obtenir la chaîne suivante :
"fonction (service, routeur, autre) {
var s = service();
var r = routeur();
}"
De cette façon, tant que nous utilisons cette méthode, nous pouvons facilement obtenir les paramètres souhaités et, plus important encore, leurs noms. C'est également la méthode utilisée par AngularJS pour implémenter l'injection de dépendances. Dans le code AngularJS, on peut voir l'expression régulière suivante :
/^fonctions*[^(]*(s*([^)]*))/m
Nous pouvons modifier la méthode de résolution avec le code ci-dessous :
résoudre : fonction() {
var func, deps, scope, args = [], self = this;
func = arguments[0];
deps = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, '').split(' ,');
portée = arguments[1] || {};
Fonction de retour() {
var a = Array.prototype.slice.call(arguments, 0);
pour(var i=0; i
var d = deps[i];
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
>
func.apply(scope || {}, args);
>
Nous utilisons l'expression régulière ci-dessus pour correspondre à la fonction que nous avons définie, et nous pouvons obtenir les résultats suivants :
["fonction (service, routeur, autre)", "service, routeur, autre"]
À ce stade, nous n’avons besoin que du deuxième élément. Mais une fois que nous avons supprimé les espaces supplémentaires et divisé la chaîne par , nous obtenons le tableau deps. Le code suivant est la partie que nous avons modifiée :
var a = Array.prototype.slice.call(arguments, 0);
...
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
Dans le code ci-dessus, nous parcourons les projets dépendants. S'il y a des projets manquants, s'il manque des parties dans les projets dépendants, nous les récupérons à partir de l'objet arguments. Si un tableau est vide, l'utilisation de la méthode shift renverra simplement undéfini sans générer d'erreur. Jusqu'à présent, la nouvelle version de l'injecteur ressemble à ceci :
var doSomething = injector.resolve (function (service, autre, routeur) {
Expect(service().name).to.be('Service');
Expect(router().name).to.be('Router');
Attendre(autre).to.be('Autre');
});
faireQuelquechose("Autre");
Dans le code ci-dessus, nous sommes libres de mélanger l'ordre des dépendances.
Mais rien n’est parfait. Il existe un problème très sérieux avec l’injection de dépendances des méthodes réflexives. Lorsque le code est simplifié, des erreurs se produisent. En effet, au cours du processus de simplification du code, les noms des paramètres ont changé, ce qui entraînerait l'échec de la résolution des dépendances. Par exemple :
var doSomething=function(e,t,n){var r=e();var i=t()}
Nous avons donc besoin de la solution suivante, tout comme dans AngularJS :
var doSomething = injector.resolve(['service', 'router', function(service, routeur) {
}]);
Ceci est très similaire à la solution AMD que nous avons vue au début, nous pouvons donc intégrer les deux méthodes ci-dessus. Le code final est le suivant :
.
Copier le code Le code est le suivant :
var injecteur = {
dépendances : {},
registre : fonction (clé, valeur) {
this.dependencies[key] = valeur;
},
résoudre : fonction() {
var func, deps, scope, args = [], self = this;
if(typeof arguments[0] === 'string') {
func = arguments[1];
deps = arguments[0].replace(/ /g, '').split(',');
portée = arguments[2] || {};
} autre {
func = arguments[0];
deps = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, '').split(' ,');
portée = arguments[1] || {};
>
return function() {
var a = Array.prototype.slice.call(arguments, 0);
pour(var i=0; i
var d = deps[i];
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
>
func.apply(scope || {}, args);
}
>
>
这一个版本的resolve方法可以接受两个或者三个参数。下面是一段测试代码:
var doSomething = injector.resolve('router,,service', function(a, b, c) {
expect(a().name).to.be('Router');
expect(b).to.be('Autre');
expect(c().name).to.be('Service');
});
faireQuelquechose("Autre");
你可能注意到了两个逗号之间什么都没有,这并不是错误。这个空缺是留给Autre这个参数的。这就是我们控制参数顺序的方法。
结语
在上面的内容中,我们介绍了几种JavaScript中依赖注入的方法,希望本文能够帮助你开始使用依赖注入这个技巧,并且写出依赖注入风格的代码。