Deep understanding of the dependency injection _javascript techniques in JavaScript

Source: Internet
Author: User
Tags reflection


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.

I. Objectives
Imagine we have two modules. The first is to be responsible for AJAX request Services (service) and the second is routing (router).

Copy Code code as follows:

var service = function () {
return {name: ' Service '};
}
var router = function () {
return {name: ' Router '};
}

We have another function that needs to use these two modules.
Copy Code code as follows:

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
Copy Code code as follows:

var dosomething = function (service, router, other) {
var s = service ();
var r = Router ();
};

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 that can help us with this. 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 dependencies
1. Injection should accept a function and return a function that we need
2. We can't write too many things-we need to streamline the beautiful grammar
3. Injection should keep the scope of the passed function
4. The passed function should be able to accept custom parameters, not just dependency descriptions
5. The perfect list, let's implement it below.
Three, the Requirejs/amd method
You may have heard of Requirejs earlier, it is a good choice to solve the dependency injection.

Copy Code code as follows:

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.
Copy Code code as follows:

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");
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-driven development) method.
Here we start our injector module, which is a great single case pattern, so it works well in different parts of our program.
Copy Code code as follows:

var injector = {
Dependencies: {},
Register:function (key, value) {
This.dependencies[key] = value;
},
Resolve:function (Deps, func, scope) {

}
}


This is a 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.
Copy Code code as follows:

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));
}
}

Scope is optional, and Array.prototype.slice.call (arguments, 0) is necessary to convert arguments variables to true arrays. 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.

Iv. Reflection Methods
According to the definition of Wikipedia, 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 output dosomething.tostring () in the console. You will get the following string:

Copy Code code as follows:

"Function (service, router, other) {
var s = service ();
var r = Router ();
}"

The string returned by 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.
Copy Code code as follows:

/^function\s*[^\ (]*\ (\s* ([^\)]*) \)/M

We can modify the resolve code as follows:
Copy Code code as follows:

Resolve:function () {
var func, Deps, scope, args = [], self = this;
func = Arguments[0];
Deps = Func.tostring (). Match (/^function\s*[^\ (]*\ (\s*) ^\)/m) []* (/1].replace, '). 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);
}
}

The result of our execution of regular expressions is as follows:
Copy Code code 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. There is only one big change:
Copy Code code as follows:

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:
Copy Code code as follows:

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");

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 () might look like this after compression:

Copy Code code as follows:

var dosomething=function (e,t,n) {var r=e (); var i=t ()}
The solution presented by the angular team 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.
Copy Code code as follows:

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, '). 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 visitors accept two or three parameters, and if there are two arguments it is actually 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:
Copy Code code as follows:

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.

V. Direct injection of 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.

Copy Code code as follows:

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));
}
}
}

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.
Copy Code code as follows:

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");

Vi. concluding remarks
In fact, most of us have used dependency injection, but we don't realize 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.

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.