컴퓨터프로그래밍의 세계는 사실 단순한 부분을 끊임없이 추상화하고 이러한 추상화를 정리하는 과정입니다. JavaScript도 예외는 아닙니다. JavaScript를 사용하면 우리 모두는 유명한 오픈 소스 라이브러리나 프레임워크 등 다른 사람이 작성한 코드를 사용합니까? 프로젝트가 성장함에 따라 우리가 의존해야 할 모듈이 점점 더 중요해지고 있으며, 이때 이러한 모듈을 어떻게 효과적으로 구성할 것인가가 매우 중요한 문제가 되었습니다. 종속성 주입은 코드 종속성 모듈을 효과적으로 구성하는 방법에 대한 문제를 해결합니다. 종속성 주입이 매우 중요한 기능 중 하나인 유명한 프런트 엔드 프레임워크AngularJS와 같은 일부 프레임워크나 라이브러리에서 "종속성 주입"이라는 용어를 들어보셨을 것입니다. 그러나 종속성 주입은 전혀 새로운 것이 아닙니다. PHP와 같은 다른 프로그래밍 언어에는 오랫동안 존재해 왔습니다. 동시에 의존성 주입은 생각만큼 복잡하지 않습니다. 이번 글에서는 자바스크립트의 의존성 주입 개념을 알아보고 "의존성 주입 스타일" 코드를 작성하는 방법을 간단하고 쉽게 설명하겠습니다.
이제 두 개의 모듈이 있다고 가정합니다. 첫 번째 모듈은 Ajax 요청을 보내는 데 사용되고 두 번째 모듈은 라우터로 사용됩니다.
var service = function() { return { name: 'Service' }; } var router = function() { return { name: 'Router' }; }
이번에 위에서 언급한 두 모듈을 사용해야 하는 함수를 작성했습니다.
var doSomething = function(other) { var s = service(); var r = router(); };
여기서 코드를 만들기 위해 흥미로운 점 이 매개변수는 몇 가지 매개변수를 더 받아야 한다는 것입니다. 물론 위의 코드를 완벽하게 사용할 수 있지만 위의 코드는 어떤 측면에서든 유연성이 약간 떨어집니다. 사용해야 하는 모듈 이름이 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>
또는 서비스JSONService<a href="http://www.php.cn/wiki/1488.html" target="_blank">JSON</a>
어떻게 해야 하나요? 아니면 테스트 목적으로 가짜 모듈을 사용하고 싶다면 어떻게 해야 할까요? 이 시점에서는 함수 자체를 편집할 수는 없습니다. 따라서 가장 먼저 해야 할 일은 종속 모듈을 함수에 매개변수로 전달하는 것입니다. 코드는 다음과 같습니다.
var doSomething = function(service, router, other) { var s = service(); var r = router(); };
위 코드에서는 필요한 모듈을 정확하게 전달합니다. 그러나 이는 새로운 문제를 불러일으킨다. 코드의 두 부분 모두에서 doSomething
메서드를 호출한다고 가정해 보겠습니다. 이 시점에서 세 번째 종속성이 필요하면 어떻게 될까요? 이때 함수 호출 코드를 모두 편집하는 것은 현명한 생각이 아닙니다. 따라서 이를 수행하는 데 도움이 되는 코드가 필요합니다. 이것이 의존성 주입기가 해결하려고 하는 문제입니다. 이제 목표를 설정할 수 있습니다.
종속성을 등록할 수 있어야 합니다.
종속성 주입기는 함수를 받은 다음 함수를 반환해야 합니다. 필요한 리소스를 얻을 수 있는
코드는 복잡하지 않고 간단하고 친숙해야 합니다
종속성 주입자는 전이성을 유지해야 합니다 함수 범위
전달된 함수는 설명된 종속성뿐만 아니라 사용자 정의 매개변수를 받을 수 있어야 합니다.
종속성 주입 문제를 매우 잘 해결할 수 있는 라이브러리인 유명한 requirejs에 대해 들어보셨을 것입니다.
define(['service', 'router'], function(service, router) { // ... });
requirejs의 아이디어는 먼저 필요한 모듈을 설명하고 함수를 직접 작성해야 합니다. 그 중에서 매개변수의 순서가 중요합니다. 유사한 구문을 구현하는 injector
이라는 모듈을 작성해야 한다고 가정해 보겠습니다.
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");
진행하기 전에 설명해야 할 한 가지는 doSomething
의 함수 본문에서 코드의 정확성을 보장하기 위해 Expect.js 어설션 라이브러리를 사용한다는 것입니다. 여기에는 TDD(테스트 주도 개발) 개념과 비슷한 것이 있습니다.
이제 공식적으로 injector
모듈 작성을 시작합니다. 먼저 애플리케이션의 모든 부분에서 동일한 기능을 갖도록 단일체여야 합니다.
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中依赖注入的方法,希望本文能够帮助你开始使用依赖注入这个技巧,并且写出依赖注入风格的代码。
위 내용은 JavaScript의 종속성 주입 샘플 코드에 대한 자세한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!