Tôt ou tard, vous devez utiliser les résultats abstraits d'autres développeurs - c'est-à-dire que vous comptez sur le code des autres. J'aime compter sur des modules gratuits (sans dépendance), mais cela est difficile à réaliser. Même ces belles composants de boîte noire que vous créez dépendra de quelque chose de plus ou moins. C'est exactement ce qui rend l'injection de dépendance grande. La capacité de gérer efficacement les dépendances est désormais absolument nécessaire. Cet article résume mon exploration du problème et de certaines solutions.
1. Objectif
Imaginez que nous ayons deux modules. Le premier est responsable du service de demande Ajax et le second est le routeur.
La copie de code est la suivante:
var service = function () {
return {name: 'service'};
}
var routeur = fonction () {
return {name: 'router'};
}
Nous avons une autre fonction qui doit utiliser ces deux modules.
La copie de code est la suivante:
var doSomething = function (autre) {
var s = service ();
var r = router ();
};
Pour le rendre plus intéressant, cette fonction accepte un argument. Bien sûr, nous pouvons utiliser complètement le code ci-dessus, mais ce n'est évidemment pas assez flexible. Et si nous voulons utiliser Servicexml ou ServiceJson, ou si nous avons besoin de modules de test. Nous ne pouvons pas résoudre le problème en modifiant le corps de la fonction seul. Tout d'abord, nous pouvons résoudre la dépendance à travers les paramètres de la fonction. Tout de suite:
La copie de code est la suivante:
var doSomething = function (service, routeur, autre) {
var s = service ();
var r = router ();
};
Nous implémentons les fonctionnalités que nous voulons en faisant passer des paramètres supplémentaires, cependant, cela apporte de nouveaux problèmes. Imaginez si notre méthode de dosage est dispersée dans notre code. Si nous devons modifier les conditions de dépendance, il nous est impossible de modifier tous les fichiers appelant la fonction.
Nous avons besoin d'un outil qui peut nous aider à faire ces choses. C'est le problème que l'injection de dépendance essaie de résoudre. Noons certains des objectifs que notre solution d'injection de dépendance devrait atteindre:
Nous devrions pouvoir enregistrer les dépendances
1. L'injection devrait accepter une fonction et renvoyer une fonction dont nous avons besoin
2. Nous ne pouvons pas trop écrire - nous devons rationaliser la belle grammaire
3. L'injection devrait maintenir la portée de la fonction transférée
4. La fonction passée devrait être en mesure d'accepter les paramètres personnalisés, et pas seulement de dépendre des descriptions
5. Une liste parfaite, réalisons-le ci-dessous.
3. Méthode requisejs / AMD
Vous avez peut-être entendu parler de requirejs, ce qui est un bon choix pour résoudre l'injection de dépendance.
La copie de code est la suivante:
Define (['Service', 'Router'], fonction (service, routeur) {
// ...
});
L'idée est de décrire d'abord les dépendances requises, puis d'écrire votre fonction. L'ordre des paramètres ici est très important. Comme mentionné ci-dessus, écrivons un module appelé injecteur qui peut accepter la même syntaxe.
La copie de code est la suivante:
var doSomething = injector.resolve (['service', 'router'], fonction (service, routeur, autre) {
attendre (service (). nom) .to.be ('service');
attendre (router (). nom) .to.be ('routeur');
attendre (autre) .to.be («autre»);
});
Dosomething ("autre");
Avant de continuer, je devrais expliquer clairement le contenu de l'organisme de fonction de dossier. méthode.
Commençons notre module d'injecteur, qui est un excellent singleton, donc il fonctionne bien dans différentes parties de notre programme.
La copie de code est la suivante:
var injecteur = {
dépendances: {},
registre: fonction (clé, valeur) {
this.dependces [key] = valeur;
},
Resolve: Fonction (Deps, Func, Scope) {
}
}
Il s'agit d'un objet très simple, avec deux méthodes, une pour stocker la propriété. Ce que nous voulons faire, c'est vérifier le tableau DEPS et rechercher des réponses dans la variable des dépendances. Tout ce qui reste est d'appeler la méthode .Apply et de passer les paramètres de la méthode FUNC précédente.
La copie de code est la suivante:
Resolve: Fonction (Deps, Func, Scope) {
var args = [];
pour (var i = 0; i <depS.length, d = deps [i]; i ++) {
if (this.dependces [d]) {
args.push (this.dependces [d]);
} autre {
lancer une nouvelle erreur ('can /' t résolver '+ d);
}
}
return function () {
func.Apply (scope || {}, args.concat (array.prototype.slice.call (arguments, 0)));
}
}
La portée est facultative, array.prototype.slice.call (arguments, 0) est nécessaire pour convertir les variables d'arguments en tableaux réels. Jusqu'à présent, ce n'est pas mal. Notre test a réussi. Le problème avec cette implémentation est que nous devons écrire les pièces requises deux fois et nous ne pouvons pas confondre leur commande. Les paramètres personnalisés supplémentaires sont toujours à l'origine de la dépendance.
4. Méthode de réflexion
Selon la définition de Wikipedia, la réflexion fait référence à la capacité d'un programme à vérifier et à modifier la structure et le comportement d'un objet lors de l'exécution. Autrement dit, dans le contexte de JavaScript, cela fait spécifiquement référence au code source d'un objet ou d'une fonction lu et analysé. Termissons la fonction de dosage mentionnée au début de l'article. Si vous publiez dosomething.toString () dans la console. Vous obtiendrez la chaîne suivante:
La copie de code est la suivante:
"Fonction (service, routeur, autre) {
var s = service ();
var r = router ();
} "
La chaîne renvoyée par cette méthode nous donne la possibilité de parcourir les paramètres et, plus important encore, d'obtenir leurs noms. Il s'agit en fait de la méthode d'Angular pour mettre en œuvre son injection de dépendance. J'étais un peu paresseux et j'ai directement intercepté l'expression régulière qui obtient les paramètres dans le code angulaire.
La copie de code est la suivante:
/ ^ fonction / s * [^ / (] * / (/ s * ([^ /)] *) /) / m
Nous pouvons modifier le code de résolution comme ceci:
La copie de code est la suivante:
résolution: fonction () {
Var Func, Deps, Scope, Args = [], self = this;
func = arguments [0];
deps = func.ToString (). Match (/ ^ fonction / s * [^ / (] * / (/ s * ([^ /)] *) /) / m) [1] .replace (/ / g, '').diviser(',');
scope = arguments [1] || {};
return function () {
var a = array.prototype.slice.call (arguments, 0);
pour (var i = 0; i <depS.length; i ++) {
var d = DEPS [i];
args.push (self.dependces [d] && d! = ''? self.dependces [d]: a.shift ());
}
func.Apply (scope || {}, args);
}
}
Le résultat de notre exécution d'expressions régulières est la suivante:
La copie de code est la suivante:
["Fonction (service, routeur, autre)", "service, routeur, autre"]
Il semble que nous n'avons besoin que du deuxième élément. Une fois que nous effacerons les espaces et divisé la chaîne, nous obtenons le tableau DEPS. Il n'y a qu'un seul grand changement:
La copie de code est la suivante:
var a = array.prototype.slice.call (arguments, 0);
...
args.push (self.dependces [d] && d! = ''? self.dependces [d]: a.shift ());
Nous parcourons le tableau des dépendances et essayons de l'obtenir à partir de l'objet Arguments si nous trouvons des éléments manquants. Heureusement, lorsque le tableau est vide, la méthode Shift renvoie simplement indéfinie au lieu de lancer une erreur (c'est grâce à l'idée du Web). La nouvelle version de l'injecteur peut être utilisée comme ce qui suit:
La copie de code est la suivante:
var doSomething = injector.resolve (fonction (service, autre, routeur) {
attendre (service (). nom) .to.be ('service');
attendre (router (). nom) .to.be ('routeur');
attendre (autre) .to.be («autre»);
});
Dosomething ("autre");
Il n'est pas nécessaire de réécrire les dépendances et leur commande peut être perturbée. Cela fonctionne toujours, et nous avons copié avec succès la magie d'Angular.
Cependant, cette pratique n'est pas parfaite, ce qui est un très gros problème avec l'injection de type réflexe. La compression détruira notre logique car elle modifie le nom du paramètre et nous ne pourrons pas maintenir la bonne relation de mappage. Par exemple, Dosometting () pourrait ressembler à ceci après la compression:
La copie de code est la suivante:
var dosomething = function (e, t, n) {var r = e (); var i = t ()}
La solution proposée par l'équipe angulaire ressemble:
var doSomething = injector.resolve (['service', 'routeur', fonction (service, routeur) {
}]);
Cela ressemble beaucoup à la solution avec laquelle nous avons commencé. Je n'ai pas pu trouver une meilleure solution, j'ai donc décidé de combiner les deux. Voici la version finale de l'injecteur.
La copie de code est la suivante:
var injecteur = {
dépendances: {},
registre: fonction (clé, valeur) {
this.dependces [key] = valeur;
},
résolution: fonction () {
Var Func, Deps, Scope, Args = [], self = this;
if (typeof arguments [0] === 'String') {
func = arguments [1];
deps = arguments [0] .replace (/ / g, '') .split (',');
scope = arguments [2] || {};
} autre {
func = arguments [0];
deps = func.ToString (). Match (/ ^ fonction / s * [^ / (] * / (/ s * ([^ /)] *) /) / m) [1] .replace (/ / g, '').diviser(',');
scope = arguments [1] || {};
}
return function () {
var a = array.prototype.slice.call (arguments, 0);
pour (var i = 0; i <depS.length; i ++) {
var d = DEPS [i];
args.push (self.dependces [d] && d! = ''? self.dependces [d]: a.shift ());
}
func.Apply (scope || {}, args);
}
}
}
Les visiteurs de résolution acceptent deux ou trois paramètres, s'il y a deux paramètres, c'est en fait la même chose que ce qui a été écrit dans l'article précédent. Cependant, s'il y a trois paramètres, il convertit le premier paramètre et remplit le tableau DEPS, voici un exemple de test:
La copie de code est la suivante:
var doSomething = injector.resolve ('routeur ,, service', fonction (a, b, c) {
attendre (a (). nom) .to.be ('routeur');
attendre (b) .to.be («autre»);
attendre (c (). nom) .to.be ('service');
});
Dosomething ("autre");
Vous remarquerez peut-être qu'il y a deux virgules après le premier paramètre - notez qu'il ne s'agit pas d'une faute de frappe. Une valeur nulle représente en fait le paramètre "autre" (placeholder). Cela montre comment nous contrôlons l'ordre des paramètres.
5. Injection directe de portée
Parfois, j'utilise la troisième variable d'injection, qui implique la portée de la fonction de fonctionnement (en d'autres termes, cet objet). Par conséquent, cette variable n'est pas nécessaire dans de nombreux cas.
La copie de code est la suivante:
var injecteur = {
dépendances: {},
registre: fonction (clé, valeur) {
this.dependces [key] = valeur;
},
Resolve: Fonction (Deps, Func, Scope) {
var args = [];
scope = scope || {};
pour (var i = 0; i <depS.length, d = deps [i]; i ++) {
if (this.dependces [d]) {
Scope [d] = this.dependces [d];
} autre {
lancer une nouvelle erreur ('can /' t résolver '+ d);
}
}
return function () {
func.Apply (scope || {}, array.prototype.slice.call (arguments, 0));
}
}
}
Tout ce que nous faisons, c'est ajouter des dépendances à la portée. L'avantage est que les développeurs n'ont plus à écrire des paramètres de dépendance;
La copie de code est la suivante:
var doSomething = injector.resolve (['service', 'router'], fonction (autre) {
attendre (this.service (). name) .to.be ('service');
attendez (this.router (). Nom) .to.be ('routeur');
attendre (autre) .to.be («autre»);
});
Dosomething ("autre");
6. Conclusion
En fait, la plupart d'entre nous ont utilisé l'injection de dépendance, mais nous ne le réalisons pas. Même si vous ne connaissez pas le terme, vous l'avez peut-être utilisé dans votre code un million de fois. J'espère que cet article approfondira votre compréhension de celui-ci.