JavaScript dependency Injection

Source: Internet
Author: User

I like to quote this sentence, "a program is a management of complexity ". The computer world is a huge abstract building group. We simply wrap some things and then release new tools to repeat them. Now let's think about some built-in abstract functions or low-level operators in your language. This is the same in JavaScript. Sooner or later, you need to use the abstract results of other developers-that is, you rely on others' code. I like to rely on free (no dependency) modules, but that is hard to implement. Even the beautiful black box components you have created will depend more or less on something. This is exactly what dependency injection shows. It is absolutely necessary to effectively manage dependencies. This article summarizes my exploration of the problem and some solutions. We have two modules. The first is the Ajax request service, and the second is the router ). Var service = function () {return {name: 'service'};} var router = function () {return {name: 'router '};} another function needs to use these two modules. Var doSomething = function (other) {var s = service (); var r = router () ;}; to make it look more interesting, this function accepts a parameter. Of course, we can use the above Code, but this is obviously not flexible enough. If we want to use ServiceXML or ServiceJSON, or if we need some test modules. We cannot solve the problem by editing the function body. First, we can solve the dependency through the function parameters. Var doSomething = function (service, router, other) {var s = service (); var r = router ();}; we implement the functions we want by passing additional parameters. However, this will bring new problems. Imagine if our doSomething method is scattered in our code. If we need to change the dependency condition, we cannot change all the files that call the function. We need a tool that can help us solve these problems. This is the problem solved by dependency injection. Let's write down some goals that our dependency injection solution should achieve: we should be able to register dependency injection and should accept a function, and return a function we need. We cannot write too many things. We need to streamline the beautiful syntax injection. We should keep the scope of the passed function. The passed function should be able to accept custom parameters, the dependency description is not just a perfect list. Let's implement it. You may have heard about the RequireJS/AMD method, which is a good choice for solving dependency injection. Define (['service', 'router '], function (service, router ){//...}); this idea is to first describe the required dependencies and then write your function. The order of parameters is very important. As mentioned above, let's write a module called injector, which can accept the same syntax. Var doSomething = injector. resolve (['service', 'router '], function (service, router, other) {reset CT (service (). name ). to. be ('service'); route CT (router (). name ). to. be ('router '); route CT (other ). to. be ('other');}); doSomething ("Other"); before continuing, I should explain the content of the doSomething function body, and I use reverse CT. js (assertion Library) is only used to ensure that the code I write is the same behavior as I expected, reflecting a little TDD (test-driven Coffee Development) method. Next we start our injector module, which is a great Singleton mode, so it can work well in different parts of our program. Copy the code var injector = {dependencies :{}, register: function (key, value) {this. dependencies [key] = value ;}, resolve: function (deps, func, scope) {}}. Copying code is a very simple object. There are two methods, A property used for storage. All we need to do is check the deps array and search for the answer in the dependencies variable. The rest is to call the. apply method and pass the parameters of the previous func method. Copy the code 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);} the copy code scope is optional, Array. prototype. slice. call (arguments, 0) is required to convert the arguments variable to a real number. Group. So far, it is not bad. Our test passed. The problem with this implementation is that we need to write the required parts twice and we cannot confuse their order. The additional custom parameters are always after the dependency. Reflection is defined by Wikipedia. Reflection is the ability of a program to check and modify the structure and behavior of an object at runtime. Simply put, in the context of JavaScript, this refers to the source code of the object or function that is read and analyzed. Let's complete the doSomething function mentioned at the beginning of the article. If you output the doSomething. tostring () log on the console early. You will get the following string: "function (service, router, other) {var s = service (); var r = router ();} "The strings returned by this method provide us with the ability to traverse parameters. More importantly, they can be obtained by name. This is actually the method for Angular to implement its dependency injection. I am a little lazy and directly intercept the Regular Expression of parameters in Angular code. /^ Function \ s * [^ \ (] * \ (\ s * ([^ \)] *) \)/m we can modify the resolve Code as follows: copy the code 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]:. shift ();} func. apply (scope | {}, args) ;}} copy the code. The result of executing the regular expression is as follows: ["function (service, router, other)", "service, router, other "] It looks like we only need the second item. Once space is clearly defined and the string is split, the deps array is obtained. There is only one big change: var a = Array. prototype. slice. call (arguments, 0);... args. push (self. dependencies [d] & d! = ''? Self. dependencies [d]: a. shift (); we traverse the dependencies array cyclically. If a missing entry is found, we try to get it from the arguments object. Fortunately, when the array is empty, the shift method returns undefined instead of throwing an error (thanks to the web idea ). The new injector can be used as follows: var doSomething = injector. resolve (function (service, other, router) {recovery CT (service (). name ). to. be ('service'); route CT (router (). name ). to. be ('router '); route CT (other ). to. be ('other');}); doSomething ("Other"); do not have to rewrite dependencies and their order can be disrupted. It still works. We copied Angular magic. However, this approach is not perfect, which is a very big problem for reflection injection. Compression destroys our logic because it changes the parameter name and we cannot maintain the correct ing relationship. For example, doSometing () compression may look like this: var doSomething = function (e, t, n) {var r = e (); var I = t ()} the solution proposed by the Angular team looks like: var doSomething = injector. resolve (['service', 'router ', function (service, router) {}]); this looks like the solution at the beginning. I failed to find a better solution, so I decided to combine the two methods. The final version of injector is as follows. Copy the code 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]:. shift ();} func. apply (scope | {}, args) ;}} copy the code resolve. The visitor accepts two or three parameters. If there are two parameters, they are actually the same as the one written earlier in the article. However, if there are three parameters, it will convert the first parameter and fill the deps array. The following is a test example: var doSomething = injector. resolve ('router, service', function (a, B, c) {analyze CT ((). name ). to. be ('router '); CT (B ). to. be ('other'); CT (c (). name ). to. be ('service') ;}); doSomething ("Other"); you may notice that there are two commas after the first parameter -- note that this is not a mistake. The null value actually represents the "Other" parameter (placeholder ). This shows how we control the Parameter order. Directly Injecting Scope sometimes I will use the third injection variable, which involves the Scope of the operation function (in other words, this object ). Therefore, you do not need to use this variable. Copy the code var injector = {dependencies :{}, register: function (key, value) {this. dependencies [key] = value ;}, resolve: function (deps, func, scope) {var args = []; scope = scope | {}; for (var I = 0; I <deps. length, d = deps [I]; I ++) {if (this. dependencies [d]) {scope [d] = this. dependencies [d];} else {throw new Error ('can \'t resolve' + d);} return function () {func. apply (scope | |{}, Array. prototype. Slice. call (arguments, 0) ;}} copy the code. All we do is add dependencies to the scope. The advantage of doing so is that developers no longer need to write dependency parameters; they are already part of the function scope. Var doSomething = injector. resolve (['service', 'router '], function (other) {reset CT (this. service (). name ). to. be ('service'); CT (this. router (). name ). to. be ('router '); route CT (other ). to. be ('other');}); doSomething ("Other"); In conclusion, most of us have used dependency injection, but we didn't realize it. Even if you don't know the term, you may have used it millions of times in your code. I hope this article will help you better understand it. The examples mentioned in this article can be found here.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.