Home > Article > Web Front-end > JavaScript dependency injection implementation
With the popularity of AngularJS, dependency injection has begun to gain a lot of attention in the JavaScript field. The most prominent benefit of DI is the development of reusable and testable code units. This article uses simple code to explain the implementation mechanism of DI. For more discussion on the advantages and disadvantages of DI, please refer to: When should you use dependency injection?
Each module declares its own dependencies and provides its own services. For example:
di.service('foo', ['bar'], function foo(bar){ function Foo(){ this.bar = bar; } this.prototype.greeting = function(){ console.log('hello, world'); } return Foo; }); var foo = di.container.get('foo'); foo.greeting();
Note the difference between dependency injection and CommonJS (or AMD). foo only needs to declare its dependency bar without actively obtaining it. It is this that makes function foo completely ignorant of the location and construction method of dependencies, making function foo a testable and reusable unit of code.
Registering services and using services should be carried out at different times. As a special dependency resolution tool, the DI framework divides the life cycle of a software unit into a registration phase and a running phase. In the above example, foo and bar services are provided during the registration phase, and these services are obtained and used during the runtime phase. Most DI frameworks adopt a lazy construction strategy, which also avoids the difficulty of construction during the registration phase.
Service customization can be performed after the registration phase and before the running phase. AngularJS 1 introduces the configuration phase to customize these services, and its Provider can be understood as a specialized factory object. BottleJS uses decorators and middleware to support customization of services.
Use IoC containers to index service instances or store service providers. When someone provides a service, it is added to the container. When someone uses the service, the provider is found from the container and a service instance is generated. Often service instances can be cached.
First implement the most common interface function .service(), which is used to register the constructor of a service. The function passed in will be new operated.
var di = { container: {} }; di.service = function(name, Constructor) { defineLazyProperty(name, () => new Constructor()); }; function defineLazyProperty(name, getter){ Object.defineProperty(di.container, name, { configurable: true, get: function() { var obj = getter(container); Object.defineProperty(di.container, name, { configurable: false value: obj }); return obj; } }); }
Object.defineProperty is used here for service caching. The constructor is only called the first time the service is built, and subsequent accesses directly read the properties of the IoC container. It is a standard method of ES5 and has very good compatibility. With the defineLazyProperty() method, the implementation of these commonly used registration interfaces is very intuitive:
di.factory = function(name, factory) { return defineLazyProperty(name, factory); }; di.provider = function(name, Provider) { return defineLazyProperty(name, function(){ var provider = new Provider(); return provider.$get(); }); }; di.value = function(name, val) { return defineLazyProperty(name, () => val); };
The customized interface of the service will not be described in detail. It is worth mentioning that unified service customization requires a unified service construction method. , instead of calling .defineLazyProperty() directly to generate the property. These strategies in AngularJS are implemented by Provider, and all other service registration methods are implemented by Provider.
The above is the content implemented by JavaScript dependency injection. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!