The dependency injection in JavaScript _javascript skills

Source: Internet
Author: User
Tags reflection

The world of computer programming is a process of abstracting simple parts and organizing them. JavaScript is no exception, when we use JavaScript to write applications, we will use the code written by others, such as some well-known open source library or framework. As our projects grow, we need to rely on more and more modules, and this time, how to effectively organize these modules has become a very important issue. Dependency injection solves the problem of how to effectively organize code-dependent modules. You may have heard of the word "dependency injection" in some framework or library species, such as the famous front-end framework Angularjs, and dependency injection is one of the most important features. However, dependency injection is not a novelty at all, and it has existed for a long time in other programming languages such as PHP. At the same time, dependency injection is not as complex as imagined. In this article, we'll take a look at the concept of dependency injection in JavaScript, explaining how to write "Dependency injection style" code in simple terms.

Goal Setting

Let's say we now have two modules. The first module is to send AJAX requests, and the second module acts as a route.

Copy Code code as follows:

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

At this point, we have written a function that uses the two modules mentioned above:
Copy Code code as follows:

var dosomething = function (Other) {
var s = service ();
var r = Router ();
};

Here, in order for our code to be interesting, this parameter needs to receive several more parameters. Of course, we can use the code above, but the code looks slightly less flexible in any way. What if the module name we need to use becomes servicexml or Servicejson? Or what if we want to use some fake modules based on the purpose of the test. At this point, we can't just edit the function itself. So the first thing we need to do is pass the dependent module as a parameter to the function, and the code looks like this:
Copy Code code as follows:

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

In the code above, we have completely passed the modules we need. But that brings up a new problem. Suppose we call the DoSomething method in the brother part of the code. Now, what if we need a third dependency? At this point, it's not a sensible way to edit all the function call codes. So, we need a piece of code to help us do this thing. This is the problem that relies on the injector to try to solve it. Now we can set our goals:

1. We should be able to register dependencies
2. The dependency injector should receive a function and then return a function that can obtain the required resources
3. Code should not be complex, but should be simple and friendly
4. The function scope that the dependency injector should keep passing
5. The passed function should be able to receive custom parameters, not just the described dependencies

Requirejs/amd method

Perhaps you've heard of the famous Requirejs, which is a good solution to the dependency injection problem:

Copy Code code as follows:

define ([' Service ', ' router '], function (service, router) {
// ...
});

The idea of Requirejs is that first we should describe the required module and then write your own function. Where the order of the parameters is important. Let's say we need to write a module called Injector, which implements similar 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 continuing, the point to note is that we use the Expect.js assertion library in the DoSomething function body to ensure that the code is correct. Here's a little bit like the idea of TDD (test-driven development).

Now we are officially starting to write our injector module. First it should be a monomer so that it can have the same function in every part of our application.

Copy Code code as follows:

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

}
}


This object is very simple, containing only two functions and a variable for storing the purpose. What we need to do is check the deps array and then find the answer in the dependencies variable species. The remaining part is to use the. Apply method to invoke the Func variable we pass:
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));
}
}

If you need to specify a scope, the above code will work as well.

In the above code, the role of Array.prototype.slice.call (arguments, 0) is to convert the arguments variable to a true array. So far, our code has been able to pass the test perfectly. But the problem here is that we have to write down the required modules two times and not be able to arrange them in random order. The extra parameters are always ranked after all dependencies.

Reflection (Reflection) method

According to Wikipedia, Reflection (reflection) refers to the process in which an object can modify its structure and behavior. In JavaScript, it is simply the ability to read an object's source code and analyze the source code. or back to our dosomething method, if you call the Dosomething.tostring () method, you can get the following string:

Copy Code code as follows:

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

So, with this approach, we can easily get the parameters we want and, more importantly, their names. This is also the method used by Angularjs to implement dependency injection. In the ANGULARJS code, we can see the following regular expression:
Copy Code code as follows:

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

We can modify the Resolve method to the code shown below:

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

We use the regular expression above to match the function we defined, and we can get the following result:

Copy Code code as follows:

["Function (service, router, other)", "service, router, other"]

At this point, we only need the second item. But once we've removed the extra spaces and split the strings, we get the deps array. The following code is the part of our modification:
Copy Code code as follows:

var a = Array.prototype.slice.call (arguments, 0);
...
Args.push (Self.dependencies[d] && d!= '? Self.dependencies[d]: A.shift ());

In the code above, we iterate over the dependencies, and if there are missing items, we get them from the arguments object if there are missing parts in the dependency project. If an array is an empty array, then using the Shift method will only return undefined without throwing an error. So far, the new version of Injector looks like this:

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

In the code above, we can randomly confuse the order of dependencies.

But nothing is perfect. There is a very serious problem with the dependency injection of the reflection method. An error occurs when the code is simplified. This is because the name of the parameter changes during code simplification, which causes the dependencies to be unresolved. For example:

Copy Code code as follows:

var dosomething=function (e,t,n) {var r=e (); var i=t ()}

So we need the following solution, just like in Angularjs:
Copy Code code as follows:

var dosomething = injector.resolve ([' Service ', ' router ', function (service, router) {

}]);


This is similar to the AMD solution that was first seen, so we can combine the above two approaches, and the final code looks like this:
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);
}
}
}

This version of the Resolve method can accept two or three parameters. Here is a section of the test code:

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 is nothing between the two commas, which is not a mistake. This vacancy is reserved for other parameters. That's how we control the order of the parameters.

Conclusion

In the above, we introduced several ways to rely on injection in JavaScript, and hopefully this article will help you get started with the technique of dependency injection and write code that relies on injection style.

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.