One of the most common technical tools for implementing control reversal in object-oriented programming (inversion, hereinafter called IOC) is that dependency injection (Dependency injection, hereinafter called DI) is a great way to go in OOP programming. For example, in the Java EE, there is the famous leading spring. There are some positive attempts in the JavaScript community, and the well-known Angularjs are largely based on di. Unfortunately, as a dynamic language that lacks a reflective mechanism and does not support annotation syntax, JavaScript has long had no spring framework of its own. Of course, with the ECMAScript of the draft into a rapid iteration of the spring breeze, the JavaScript community of various dialects, frames are the warlords and rise, in the ascendant. It can be foreseen that the emergence of the excellent JAVASCRIPTDI framework is only a matter of morning and evening.
This paper summarizes the common dependency injection methods in JavaScript and, taking Inversify.js as an example, introduces the attempts and preliminary results of the dialect community in the JavaScript di framework. The article is divided into four sections:
I. Dependency injection based on injector, cache, and function parameter names
Two. Angularjs based on double injector Dependency Injection
Three. Typescript dependency injection based on adorner and reflection
Four. IOC container in the Inversify.js--javascript technology stack
I. Dependency injection based on injector, cache, and function parameter names
Although there is no native support for reflection (Reflection) syntax in JavaScript, But the ToString method on the Function.prototype makes it possible for us to pry into the internal construction of a function at run time: The ToString method returns the entire function definition, including the functions keyword, as a string. From this complete function definition, we can use the regular expression to extract the parameters required by the function, and to some extent, we know the operation dependence of the function.
The function signature Write (notebook, pencil) of the Write method on the student class indicates that its execution relies on notebook and pencil objects. Therefore, we can first store the notebook and pencil objects in a cache, and then through the injector (injector, syringe) to the Write method to provide the dependency it needs:
var cache = ();
// Get the parameter name by parsing Function.prototype.toString ()
function getParamNames (func) {
// The regular expression is from http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
var paramNames = func.toString (). match (/ ^ function \ s * [^ \ (] * \ (\ s * ([^ \)] *) \) / m) [1];
paramNames = paramNames.replace (/ / g, '');
paramNames = paramNames.split (',');
return paramNames;
}
var injector = {
// Bind the this keyword in the func scope to the bind object, the bind object can be empty
resolve: function (func, bind) {
// get parameter name
var paramNames = getParamNames (func);
var params = [];
for (var i = 0; i <paramNames.length; i ++) {
// Take out the corresponding dependencies in the cache by parameter name
params.push (cache [paramNames [i]]);
}
// Inject dependencies and execute functions
func.apply (bind, params);
}
};
function Notebook () {}
Notebook.prototype.printName = function () {
console.log ('this is a notebook');
};
function Pencil () {}
Pencil.prototype.printName = function () {
console.log ('this is a pencil');
};
function Student () {}
Student.prototype.write = function (notebook, pencil) {
if (! notebook ||! pencil) {
throw new Error ('Dependencies not provided!');
}
console.log ('writing ...');
};
// provide notebook dependencies
cache ['notebook'] = new Notebook ();
// provide pencil dependency
cache ['pencil'] = new Pencil ();
var student = new Student ();
injector.resolve (student.write, student); // writing ...
Sometimes in order to ensure good encapsulation, it is not necessary to expose the cache object to the external scope, more often in the form of closures or private attributes:
function Injector () {
this._cache = ();
}
Injector.prototype.put = function (name, obj) {
this._cache [name] = obj;
};
Injector.prototype.getParamNames = function (func) {
// The regular expression is from http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
var paramNames = func.toString (). match (/ ^ function \ s * [^ \ (] * \ (\ s * ([^ \)] *) \) / m) [1];
paramNames = paramNames.replace (/ / g, '');
paramNames = paramNames.split (',');
return paramNames;
};
Injector.prototype.resolve = function (func, bind) {
var self = this;
var paramNames = self.getParamNames (func);
var params = paramNames.map (function (name) {
return self._cache [name];
});
func.apply (bind, params);
};
var injector = new Injector ();
var student = new Student ();
injector.put ('notebook', new Notebook ());
injector.put ('pencil', new Pencil ())
injector.resolve (student.write, student); // writing ...
For example, now perform another method on the student class, function draw (notebook, pencil, eraser), because injector and notebook objects are already in the cache of pencil. We just need to store the extra eraser in the cache:
function Eraser () {}
Eraser.prototype.printName = function () {
Console.log (' This is a Eraser ');
Add Draw method for Student
Student.prototype.draw = function (notebook, pencil, eraser) {
if (!notebook | |!pencil | |!er Aser) {
throw new Error (' Dependencies not provided! ');
}
Console.log (' drawing ... ');
Injector.put (' Eraser ', New Eraser ());
Injector.resolve (Student.draw, student);
By relying on injection, the execution of functions and the creation logic of the objects on which they are dependent are decoupled.
Of course, with the popularization of Grunt/gulp/fis and other front-end engineering tools, more and more projects in the line before the code confusion (uglify), so through the parameter name to judge dependence is not always reliable, It is also sometimes a clear indication of its dependencies by adding additional attributes to the function:
Student.prototype.write.depends = [' notebook ', ' pencil '];
Student.prototype.draw.depends = [' notebook ', ' pencil ', ' eraser '];
Injector.prototype.resolve = function (func, bind) {
var self = this;
First check whether there are depends properties on the Func, if not, then use regular expressions to parse
func.depends = Func.depends | | self.getparamnames (func);
var params = Func.depends.map (function (name) {return
self._cache[name];
});
Func.apply (bind, params);
var student = new Student ();
Injector.resolve (student.write, student);
writing ... Injector.resolve (Student.draw, student); Draw ...
Two. Angularjs based on double injector Dependency Injection
Students who are familiar with ANGULARJS will soon be able to recall that, before injector injection, we can also call the Config method to configure the objects that will then be injected when the module is defined. A typical example is the configuration of $routeprovider when using routing. That is, unlike in the previous section where ready-made objects (such as New Notebook ()) are stored directly in cache, dependency injection in ANGULARJS should also have a process of "instantiating" or "invoking factory methods."
This is the origin of Providerinjector, Instanceinjector and their respective providercache and Instancecache.
In Angularjs, the injector that we can get through dependency injection is usually instanceinjector, while Providerinjector is in the form of a variable in the closure. Whenever we need angularjs to provide dependency injection services, such as trying to get notebook,instanceinjector, we will first query the notebook attribute on the Instancecache, if it exists, direct injection; Then handing this task to Providerinjector;providerinjector will stitch the "Provider" string to the back of the "notebook" string, forming a new key name "Notebookprovider", Again to Providercache query whether there is notebookprovider this attribute, if there is no, then throw an exception unknown provider exception:
If there is, then return this provider to Instanceinjector;instanceinjector to get Notebookprovider, will call the factory method on Notebookprovider $get, Gets the return value notebook object, puts the object in the Instancecache for future use, and injects it into a function that initially declares the dependency.
Note that the dependency injection approach in ANGULARJS is also flawed: the global side effect of using a single instanceinjector service is that it is not possible to track and control a dependent chain alone, even without cross dependencies Provider with the same name in different module will also produce overlays, which are not expanded in detail here.
In addition, for students accustomed to high-level IOC containers in languages such as Java and C #, it may seem awkward to see here, after all, in oop, we usually do not pass dependencies as arguments to the method, but rather as properties passed to the instance by constructor or setters, To implement encapsulation. Indeed, the dependency injection approach in section one or two does not embody enough object-oriented features, which have existed in JavaScript for many years and do not even require ES5 syntax support. Students who want to learn about the research and results of dependency injection over the last year or two in the JavaScript community can continue reading.
Three. Typescript dependency injection based on adorner and reflection
Bloggers themselves are not particularly enthusiastic about the various dialects of JavaScript, especially now Emcascript proposals, drafts updated quickly, many times with the help of Polyfill and Babel preset can meet the needs. But Typescript is an exception (of course now decorator is already a proposal, although the stage is relatively early, but indeed there are polyfill can be used). As mentioned above, there is a lingering lack of a good IOC container in the JavaScript community and its own language features, so what difference does typescript bring to us on the subject of injection? There are at least the following points:
* Typescript adds compile-time type checking to allow JavaScript to have a certain static language feature
* Typescript support Adorner (decorator) syntax, similar to traditional annotations (Annotation)
* Typescript support meta information (METADATA) reflection, no longer need to invoke the Function.prototype.toString method
Here we try to standardize and simplify dependency injection with the new syntax brought by typescript. This time we no longer inject dependency into a function or method, but inject it into the constructor of the class.
Typescript supports the decoration of classes, methods, properties, and function parameters, where you need to decorate the class. Continue with the examples in the above section, using Typescript to refactor the code:
class Pencil {public Printname () {Console.log (' a Pencil ');
} class Eraser {public Printname () {console.log (' the ' is a Eraser ');
} class Notebook {public Printname () {Console.log (' a notebook ');
} class Student {pencil:pencil;
Eraser:eraser;
Notebook:notebook;
Public constructor (Notebook:notebook, Pencil:pencil, eraser:eraser) {This.notebook = notebook;
This.pencil = pencil;
This.eraser = eraser;
Public write () {if (!this.notebook | |!this.pencil) {throw new Error (' Dependencies not provided! ');
} console.log (' writing ... '); Public Draw () {if (!this.notebook | |!this.pencil | |!this.eraser) {throw new Error (' dependencies not prov
Ided! ');
} console.log (' Drawing ... '); }
}
The following is the implementation of the injector and adorner inject. Injector's Resolve method, when it receives an incoming constructor, takes the name of the constructor from the Name property, such as Class Student, whose Name property is the string "Student". Then the student as key, to Dependenciesmap to remove the student dependency, as to Dependenciesmap when the dependency, which is the adorner inject logic, will be discussed later. Student dependencies are taken out, because these dependencies are already references to constructors rather than simple strings (such as notebook, pencil constructors), so you can get those objects directly using the new statement. After you get to the objects that the student class relies on, how do you pass these dependencies into the student as parameters for the constructor? The simplest is ES6 's spread operator. In an environment where ES6 cannot be used, we can also complete the above logic by forging a constructor function. Note In order for the instanceof operator to not fail, the prototype property of this bogus constructor should point to the prototype property of the original constructor.
var dependenciesmap = {};
var injector = {
Resolve:function (constructor) {
var dependencies = Dependenciesmap[constructor.name];
dependencies = Dependencies.map (function (dependency) {return
new dependency ();
});
If you can use ES6 syntax, the following code can be merged into one line:
//return new constructor (... dependencies);
var mockconstructor:any = function () {
constructor.apply (this, dependencies);
Mockconstructor.prototype = Constructor.prototype;
return new Mockconstructor ();
}
;
function inject (... dependencies) {return
function (constructor) {
Dependenciesmap[constructor.name] = dependencies;
return constructor;}
After the logic of the injector and adorner inject is complete, you can use it to decorate class student and enjoy the pleasure of dependency injection:
Using adorners, we can also implement a more radical dependency injection, hereinafter referred to as Radicalinject. Radicalinject to the original code intrusion is relatively strong, not necessarily suitable for specific business, here also introduced. To understand radicalinject, it is necessary to understand the principle of the typescript adorner and the reduce method on the Array.prototype.
function Radicalinject (... dependencies) {
var wrappedfunc:any = function (target:any) {
dependencies = Dependencies.map (function (dependency) {return
new dependency ();
});
The reason for using Mockconstructor is the same
function Mockconstructor () {
target.apply (this, dependencies) as the previous example;
Mockconstructor.prototype = Target.prototype;
Why do you need to use Reservedconstructor? Since the student method was decorated with radicalinject, the
//Student Point constructor is not the class student we declared at the beginning, but the return value here.
namely Reservedconstructor. Student's point of change is not an acceptable thing to do, but if you want to/
guarantee student instanceof student work as we expect, it should be
The Reservedconstructor prototype attribute points to the prototype function of the original student
Reservedconstructor () {return
new Mockconstructor ();
}
Reservedconstructor.prototype = Target.prototype;
return reservedconstructor;
}
return wrappedfunc;
}
Using Radicalinject, the original constructor has essentially been represented by a new function, and is simpler to use, and does not even require a injector implementation:
@RadicalInject (notebook, Pencil, Eraser) class Student {pencil:pencil;
Eraser:eraser;
Notebook:notebook; Public constructor () {} public constructor (Notebook:notebook, Pencil:pencil, eraser:eraser) {This.notebook = not
ebook
This.pencil = pencil;
This.eraser = eraser;
Public write () {if (!this.notebook | |!this.pencil) {throw new Error (' Dependencies not provided! ');
} console.log (' writing ... '); Public Draw () {if (!this.notebook | |!this.pencil | |!this.eraser) {throw new Error (' dependencies not prov
Ided! ');
} console.log (' Drawing ... ');
}//no longer appears injector, direct call constructor var student = new Student (); Console.log (student instanceof Student); True Student.notebook.printName (); This is a notebook student.pencil.printName (); This is a pencil student.eraser.printName (); This is a eraser student.draw (); Drawing Student.write (); Writing @RadicalInject (Notebook, Pencil, Eraser)
class Student {
pencil: Pencil;
eraser: Eraser;
notebook; Notebook;
public constructor () {}
public constructor (notebook: Notebook, pencil: Pencil, eraser: Eraser) {
this.notebook = notebook;
this.pencil = pencil;
this.eraser = eraser;
}
public write () {
if (! this.notebook ||! this.pencil) {
throw new Error ('Dependencies not provided!');
}
console.log ('writing ...');
}
public draw () {
if (! this.notebook ||! this.pencil ||! this.eraser) {
throw new Error ('Dependencies not provided!');
}
console.log ('drawing ...');
}
}
// Injector no longer appears, call the constructor directly
var student = new Student ();
console.log (student instanceof Student); // true
student.notebook.printName (); // this is a notebook
student.pencil.printName (); // this is a pencil
student.eraser.printName (); // this is an eraser
student.draw (); // drawing
student.write (); // writing
Because the class Student constructor method needs to receive three parameters, a direct parameterless call to New Student () can cause the typescript compiler to make an error. Of course, this is just a way of sharing ideas, you can temporarily ignore this error. Interested students can also use similar ideas to try to proxy a factory method instead of a direct proxy constructor to avoid such errors, which are not expanded here.
The ANGULARJS2 team was once ready to start a new framework based on Atscript (atscript "A" refers to annotation) in order to obtain better adorner and reflective grammar support. But finally chose to embrace typescript, so there is a wonderful combination of Microsoft and Google.
Of course, it is important to note that, in the absence of relevant standards and browser vendor support, typescript is only pure JavaScript at run time, as the example in the following section confirms.
Four. IOC container in the Inversify.js--javascript technology stack
In fact, the emergence of various languages that support advanced language features from JavaScript can be foreseen, the emergence of the IOC container is only a matter of morning and evening. For example, the blogger today to introduce the typescript based on the Inversify.js, is one of the forerunners.
Inversity.js is much more advanced than the example of the blogger in the previous section, and it was originally designed to be a very high goal for the front-end engineers to write code that conforms to the solid principle in JavaScript. In the code, there is the interface everywhere, will "Depend upon abstractions." Do not depend upon concretions. " (dependent on abstraction, not on concrete) performing incisively and vividly. Continue using the example above, but since Inversity.js is interface-oriented, the above code needs to be further refactored:
Interface Notebookinterface {printname (): void; interface Pencilinterface {printname (): void;} interface Eraseri
Nterface {printname (): void;} interface Studentinterface {notebook:notebookinterface;
Pencil:pencilinterface;
Eraser:eraserinterface;
Write (): void;
Draw (): void;
Class Notebook implements Notebookinterface {public printname () {Console.log (' a notebook ');
The class Pencil implements Pencilinterface {public printname () {Console.log (' a Pencil ');
Class Eraser implements Eraserinterface {public printname () {console.log (' the ' is a Eraser ');
} class Student implements Studentinterface {notebook:notebookinterface;
Pencil:pencilinterface;
Eraser:eraserinterface;
Constructor (Notebook:notebookinterface, Pencil:pencilinterface, eraser:eraserinterface) {This.notebook = notebook;
This.pencil = pencil;
This.eraser = eraser;
Write () {console.log (' writing ... '); } DRAW () {console.log (' drawing ... ');
}
}
With the inversity framework, we don't have to implement the injector and inject adorners ourselves, we just need to refer to the relevant objects from the Inversify module:
Import {inject} from "inversify";
@Inject ("Notebookinterface", "Pencilinterface", "Eraserinterface")
class Student implements Studentinterface {
Notebook:notebookinterface;
Pencil:pencilinterface;
Eraser:eraserinterface;
Constructor (Notebook:notebookinterface, Pencil:pencilinterface, eraser:eraserinterface) {
This.notebook = notebook;
This.pencil = pencil;
This.eraser = eraser;
}
Write () {
console.log (' writing ... ');
}
Draw () {
console.log (' drawing ... ');
}
}
Is that all you got? Remember that in the last section, the concepts of typescript are just grammatical sugars? Unlike the example in the previous section where the constructor reference is passed directly to inject, the inversify.js is interface-oriented, and such as Notebookinterface, Interfaces such as pencilinterface are only syntactic sugars provided by typescript and do not exist at run time, so we can only use string form instead of reference form when declaring dependencies in adorners. But don't worry, Inversify.js provides us with a bind mechanism that bridges the string form of an interface with a specific constructor:
Import {typebinding, Kernel} from "Inversify";
var kernel = new kernel ();
Kernel.bind (New typebinding ("Notebookinterface", Notebook));
Kernel.bind (New typebinding ("Pencilinterface", Pencil));
Kernel.bind (New typebinding ("Eraserinterface", Eraser));
Kernel.bind (New typebinding ("Studentinterface", Student));
Note that this step requires the introduction of typebinding and kernel from the Inversify module, and the generic syntax is used to ensure that the return value type and the entire compile-time static type check pass smoothly.
It is natural to understand the new typebinding ("Notebookinterface", Notebook): To provide instances of the notebook class for classes that rely on the "Notebookinterface" string. The return value is traced back to the notebookinterface.
These steps have been completed, and they are also handy to use:
var student:studentinterface = kernel.resolve ("Studentinterface");
Console.log (student instanceof Student); True
student.notebook.printName ();//This are a notebook
student.pencil.printName ();//This is a pencil
s Tudent.eraser.printName (); This is a eraser
student.draw ();//Drawing
student.write ();//Writing
Finally, incidentally, the status and progress of the relevant proposals in ECMAScript. Google's Atscript team once had a annotation proposal, but Atscript was stillborn, and the proposal naturally came to a dead end. At present, more hopeful to become the ES7 standard is a proposal on the adorner: Https://github.com/wycats/javascript-decorators. Interested students can go to the relevant GitHub page to follow the understanding. Although di is only one of the many patterns and features of OOP programming, it can reflect the hard way in which JavaScript is moving on OOP. But in a way, it is a magnanimous road and a bright future. Back to the subject of dependency injection, one side is the long-awaited JavaScript community, and the late IOC container, which ultimately produces a chemical reaction, remains to be seen.