Le monde de la
Programmation informatique est en fait un processus d'abstraction constante de parties simples et d'organisation de ces abstractions. JavaScript ne fait pas exception. Lorsque nous utilisons JavaScript pour écrire des applications, utilisons-nous tous des codes écrits par d'autres, comme certaines bibliothèques open source célèbres ou des frameworks . À 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 de dépendance de code. Vous avez peut-être entendu le terme « injection de dépendances » dans certains frameworks ou bibliothèques, comme le fameux Front-end FrameworkAngularJS, où 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".
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 = function() { return { name: 'Service' }; } var router = function() { return { name: 'Router' }; }
A cette époque, nous avons écrit une fonction , qui nécessite l'utilisation des deux modules cités ci-dessus :
var doSomething = function(other) { var s = service(); var r = router(); };
Ici, pour que nous Le code devient un peu plus intéressant, ce paramètre a besoin de recevoir quelques paramètres supplémentaires. Bien sûr, nous pouvons utiliser complètement le code ci-dessus, mais le code ci-dessus est légèrement moins flexible sous tous ses aspects. Si le nom du module que nous devons utiliser devient Service<a href="http://www.php.cn/wiki/1527.html" target="_blank">XML<code>Service<a href="http://www.php.cn/wiki/1527.html" target="_blank">XML</a>
ou < code>ServiceJSONService<a href="http://www.php.cn/wiki/1488.html" target="_blank">JSON</a>
Que dois-je faire ? 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 les modules dépendants en paramètres à la fonction, le code est le suivant :
var doSomething = function(service, router, other) { var s = service(); var r = router(); };
Dans le code ci-dessus, nous passons 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 maintenant définir nos objectifs :
Nous devrions pouvoir enregistrer les dépendances
L'injecteur de dépendances doit recevoir une fonction, puis renvoyer une fonction qui peut obtenir les ressources nécessaires
Le code ne doit pas être compliqué, mais simple et convivial
L'injecteur de dépendances doit rester transitif Portée de la fonction
La fonction transmise doit pouvoir recevoir des paramètres personnalisés, pas seulement les dépendances décrites
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 :
define(['service', 'router'], function(service, router) { // ... });
L'idée de requirejs est que nous devons d'abord décrire les modules requis, puis écrire vos propres fonctions. Parmi eux, l’ordre des paramètres est important. Supposons que nous devions écrire un module appelé injector
qui implémente une syntaxe similaire.
var doSomething = injector.resolve(['service', 'router'], function(service, router, other) { expect(service().name).to.be('Service'); expect(router().name).to.be('Router'); expect(other).to.be('Other'); }); doSomething("Other");
Avant de continuer, une chose qui doit être expliquée est que dans le corps de la fonction de doSomething
, nous utilisons la bibliothèque d'assertions expect.js pour garantir l'exactitude du code. Il y a quelque chose de similaire à l'idée du TDD (test piloté développement) ici.
Maintenant, nous commençons officiellement à écrire notre module injector
. 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 injector = { dependencies: {}, register: function(key, value) { this.dependencies[key] = value; }, resolve: function(deps, func, scope) { } }
这个对象非常的简单,其中只包含两个函数以及一个用于存储目的的变量。我们需要做的事情是检查deps
数组,然后在dependencies
变量种寻找答案。剩余的部分,则是使用.apply
方法去调用我们传递的func
变量:
resolve: function(deps, func, scope) { var args = []; for(var i=0; i<deps.length, d=deps[i]; i++) { if(this.dependencies[d]) { args.push(this.dependencies[d]); } else { throw new Error('Can\'t resolve ' + d); } } return function() { func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0))); } }
如果你需要指定一个作用域,上面的代码也能够正常的运行。
在上面的代码中,Array.prototype.slice.call(arguments, 0)
的作用是将arguments
变量转换为一个真正的数组。到目前为止,我们的代码可以完美的通过测试。但是这里的问题是我们必须要将需要的模块写两次,而且不能够随意排列顺序。额外的参数总是排在所有的依赖项之后。
根据维基百科中的解释,反射(reflection)指的是程序可以在运行过程中,一个对象可以修改自己的结构和行为。在JavaScript中,简单来说就是阅读一个对象的源码并且分析源码的能力。还是回到我们的doSomething
方法,如果你调用doSomething.to<a href="http://www.php.cn/wiki/57.html" target="_blank">String</a>()
方法,你可以获得下面的字符串:
"function (service, router, other) { var s = service(); var r = router(); }"
这样一来,只要使用这个方法,我们就可以轻松的获取到我们想要的参数,以及更重要的一点就是他们的名字。这也是AngularJS实现依赖注入所使用的方法。在AngularJS的代码中,我们可以看到下面的正则表达式:
/^function\s*[^\(]*\(\s*([^\)]*)\)/m
我们可以将resolve
方法修改成如下所示的代码:
resolve: function() { var func, deps, scope, args = [], self = this; func = arguments[0]; deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(','); scope = arguments[1] || {}; return function() { var a = Array.prototype.slice.call(arguments, 0); for(var i=0; i<deps.length; i++) { var d = deps[i]; args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift()); } func.apply(scope || {}, args); } }
我们使用上面的正则表达式去匹配我们定义的函数,我们可以获取到下面的结果:
["function (service, router, other)", "service, router, other"]
此时,我们只需要第二项。但是一旦我们去除了多余的空格并以,
来切分字符串以后,我们就得到了deps
数组。下面的代码就是我们进行修改的部分:
var a = Array.prototype.slice.call(arguments, 0); ... args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
在上面的代码中,我们遍历了依赖项目,如果其中有缺失的项目,如果依赖项目中有缺失的部分,我们就从arguments
对象中获取。如果一个数组是空数组,那么使用shift
方法将只会返回undefined
,而不会抛出一个错误。到目前为止,新版本的injector
看起来如下所示:
var doSomething = injector.resolve(function(service, other, router) { expect(service().name).to.be('Service'); expect(router().name).to.be('Router'); expect(other).to.be('Other'); }); doSomething("Other");
在上面的代码中,我们可以随意混淆依赖项的顺序。
但是,没有什么是完美的。反射方法的依赖注入存在一个非常严重的问题。当代码简化时,会发生错误。这是因为在代码简化的过程中,参数的名称发生了变化,这将导致依赖项无法解析。例如:
var doSomething=function(e,t,n){var r=e();var i=t()}
因此我们需要下面的解决方案,就像AngularJS中那样:
var doSomething = injector.resolve(['service', 'router', function(service, router) { }]);
这和最一开始看到的AMD的解决方案很类似,于是我们可以将上面两种方法整合起来,最终代码如下所示:
var injector = { dependencies: {}, register: function(key, value) { this.dependencies[key] = value; }, resolve: function() { var func, deps, scope, args = [], self = this; if(typeof arguments[0] === 'string') { func = arguments[1]; deps = arguments[0].replace(/ /g, '').split(','); scope = arguments[2] || {}; } else { func = arguments[0]; deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(','); scope = arguments[1] || {}; } return function() { var a = Array.prototype.slice.call(arguments, 0); for(var i=0; i<deps.length; 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('Other'); expect(c().name).to.be('Service'); }); doSomething("Other");
你可能注意到了两个逗号之间什么都没有,这并不是错误。这个空缺是留给Other
这个参数的。这就是我们控制参数顺序的方法。
在上面的内容中,我们介绍了几种JavaScript中依赖注入的方法,希望本文能够帮助你开始使用依赖注入这个技巧,并且写出依赖注入风格的代码。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!