The dependency injection in JavaScript

Source: Internet
Author: User
Tags reflection regular expression split

  I like to quote this phrase, "program is management of complexity." The computer world is a huge and abstract complex. We simply pack something and release the new tools. Now consider the language you are using to include some built-in abstract functions or low-level operators. This is the same in Java script.

  Sooner or later you will need to use the abstract results of other developers--that is, you rely on someone else's code. I prefer modules that depend on freedom (no dependencies), but that's hard to achieve. Even the beautiful black box components that you create are more or less dependent on something. This is where reliance is injected. It is absolutely necessary to manage the ability to rely effectively. This article summarizes my exploration of the problem and some of the solutions.   Goal   Imagine we have two modules. The first is to be responsible for AJAX request Services (service) and the second is routing (router).   var service = function () {    return {name: ' Service '};} var router = function () {    Retu RN {name: ' Router '}; We have another function that needs to use these two modules.   var dosomething = function (other) {    var s = Service ()     var r = router ();}; To make it look more interesting, this function takes a parameter. Of course, we can use the above code completely, but this is obviously not flexible. If we want to use Servicexml or Servicejson, or if we need some test modules. We can't just edit the function body to solve the problem. First, we can solve the dependencies by using the parameters of the function. That is:   var dosomething = function (service, router, other) {    var s = service ();     var r = Rout ER (); }; We implement the functionality we want by passing additional parameters, however, this can create 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 to help us with these. This is the problem that dependency injection tries to solve. Let's write down some of the goals that our dependency injection solution should achieve:   We should be able to register dependency injection should accept a function and return a function we need we can't write too many things-we need to simplify the beautiful syntax injection should keep the scope of the passed function transmittedThe recursive function should be able to accept the custom parameters, not just the description of the list of the perfect, below let us implement it.   REQUIREJS/AMD method   You may have heard about Requirejs early, it is a good choice to solve the dependency injection.   Define ([' Service ', ' router '], function (service, router) {          //...}); The idea is to first describe the dependencies you need, and then write your function. The order of the parameters here is important. As mentioned above, let's write a module called Injector that accepts the same syntax.   var dosomething = injector.resolve ([' Service ', ' router '], function (service, router, other) {    expect (s Ervice (). Name). to.be (' Service ');     Expect (router (). Name). to.be (' router ');     Expect (other) to.be (' other '); }); DoSomething ("other"); Before I go any further, I should explain the dosomething function body content, I use Expect.js (assertion side of the library) only to ensure that I write the code behavior and I expected the same, a little bit of TDD (test drive the development of the CAF) method.   Start with our injector module, which is a great single case pattern, so it works well in different parts of our program.   Copy code var injector = {    dependencies: {},     register:function (key, value) {        This.dependencies[key] = value;    },     resolve:function (Deps, func, scope) {   ,  }} Copy code this is aA very simple object, there are two methods, a property to store. All we have to do is check the Deps array and search for the answer in the dependencies variable. All that remains is to invoke the. Apply method and pass the parameters of the previous Func method.   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.DEP ENDENCIES[D]);        } else {            throw new Error (' Can ' t resolve ' + D); &nbs P      }    }     return function () {        Func.apply (Scope | }, Args.concat (Array.prototype.slice.call (arguments, 0));    }        } Replication code scope is optional, Array.prototype.slice.call (arguments, 0) is required, Used to convert the arguments variable to a true array. So far it's good. We passed the test. The problem with this implementation is that we need to write the required parts two times, and we can't confuse their order. Additional custom parameters are always located after the dependency.   Reflection method   According to Wikipedia definition reflection is the ability of a program to check and modify the structure and behavior of an object at run time. Simply put, in the context of JavaScript, this refers specifically to the source code of the object or function being read and parsed. Let's complete the dosomething function mentioned at the beginning of the article. If you control it earlier,Output dosomething.tostring () log. You will get the following string:   "function (service, router, other) {    var s = service ();     var r = router (); "The string returned through this method gives us the ability to traverse the parameters and, more importantly, to get their names." This is actually a angular way to implement its dependency injection. I stole a little lazy, directly intercept the regular expression that gets the parameter in the angular code.  /^functions*[^ (]* (s* ([^)]*))/M We can modify the resolve code as follows:     Resolve:function () {    var func, Deps, scope, args = [], self = this;     func = arguments[0];     Deps = func.tostring (). Match (/^functions*[^ (]* (s* ([^)]*))/m) [1].replace (//g, ']. Split (', ');     scope = Arguments[1] | | {};     return function () {        var a = Array.prototype.slice.call (arguments, 0);   &NB Sp     for (var i=0 i<deps.length; i++) {            var d = deps[i];   &NBS P         Args.push (Self.dependencies[d] && d!= '? Self.dependencies[d]: A.shift ());        }         func.apply (Scope | | {}, args);    }        }   The results of our execution of regular expressions are as follows:   ["Function (service, router, other)", "service , router, other "] it looks like we just need a second item. Once we know the space and split the string, we get the deps array. Only one big change:   var a = Array.prototype.slice.call (arguments, 0); ... Args.push (Self.dependencies[d] && d!= '? Self.dependencies[d]: A.shift ()); We loop through the dependencies array and try to get it from the arguments object if the missing item is found. Thankfully, when the array is empty, the shift method simply returns undefined instead of throwing an error (which benefits from the web's idea). The new injector can be used as follows:   var dosomething = injector.resolve (function (service, other, router) {    expect (SE Rvice (). Name). to.be (' Service ');     Expect (router (). Name). to.be (' router ');     Expect (other) to.be (' other '); }); DoSomething ("other"); There is no need to rewrite dependencies and their order can be disrupted. It still works, and we have successfully copied the magic of angular.   However, this practice is not perfect, which is the reflection type injection a very big problem. Compression destroys our logic because it changes the name of the parameter and we will not be able to maintain the correct mapping relationship. For example, dosometing () may look like this after compression:   var dosomething=function (e,t,n) {var r=e (); var i=t ()} angular the team presents a solution that looks like this:   var dosomething = injector.resolve ([' Service ', ' router ', function (service, router) { }]); It looks very much like the solution we started with. I couldn't find a better solution, so I decided to combine the two approaches. The following is the final version of injector.     var injector = {    dependencies: {},     register:function (key, value) {  &NBSP ;     This.dependencies[key] = value;    },     resolve:function () {        var func, deps, scope, args = [], self = This         if (typeof arguments[0] = = ' string ') {            func = Argume NTS[1];             Deps = arguments[0].replace (//g, '). Split (', ');             scope = Arguments[2] | | {};        } else {            func = arguments[0];       & nbsp     Deps = func.tostring (). Match (/^functions*[^ (]* (s* ([^)]*))/m) [1].replace (//g, ']. Split (', ');            scope = Arguments[1] | | {};        }         return function () {            VA R 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.dependenc IES[D]: A.shift ());                         func.apply (Scope | | {}, args);        }            }   Resolve visitors accept two or three parameters, if there are two parameters it actually is the same as the one written earlier in the article. However, if you have three parameters, it converts the first argument and populates the Deps array, and here is a test example:   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"); You may notice that there are two commas behind the first argument--note that this is not a clerical error. A null value actually represents the "other" argument (placeholder). This shows how we control the order of parameters.   Direct injection Scope   Sometimes I use the third injection variable, which involves the scope of the action function (in other words, the This object). So, many times you don't need to use this variable.   var injector = {    dependencies: {},     register:function (key, value) {    &NBSP ;   This.dependencies[key] = value;    },     resolve:function (Deps, func, scope) {      var args = [];   &N Bsp     scope = Scope | | {};         for (Var i=0 i<deps.length, D=deps[i]; i++) {            if ( This.dependencies[d]) {                SCOPE[D] = this.dependencies[d];   &N Bsp        } else {                throw new Error (' Can ' t resolv E ' + D);             {       }         return function () {            func.apply (Scope | | {}, Array.prototype.slice.call (arguments, 0));        }            }   All we do is add dependencies to the scope. The advantage of this is that developers do not have to write dependency parameters; they are already part of a function scope.   var dosomething = injector.resolve ([' Service ', ' router '], function (other) {    expect (This.service (). Name). to.be (' Service ');     Expect (This.router (). Name). to.be (' router ');     Expect (other) to.be (' other '); }); DoSomething ("other"); Concluding remarks   In fact most of us have used dependency injection, but we are not aware of it. Even if you don't know the term, you might use it in your code for millions of years. I hope this article will deepen your understanding of 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.