This article is based on self-understanding to output, the purpose is to exchange and study, if there is something wrong, I hope you can point it out.
DI
DI-Dependency Injection, or "dependency injection": The dependency between objects is determined by the container at runtime. To put it more vividly, the container dynamically injects an object into the object properties. The purpose of dependency injection is not to bring more functions to the software system, but to increase the frequency of object reuse and build a flexible and extensible framework for the system.
How to use
First look at the common dependency injection (DI) method:
function Inject(target: any, key: string){
target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
class A {
sayHello(){
console.log('hello')
}
}
class B {
@Inject // 编译后等同于执行了 @Reflect.metadata("design:type", A)
a: A
say(){
this.a.sayHello() // 不需要再对class A进行实例化
}
}
new B().say() // hello
Principle analysis
TS decorator time compiler, either by performing multi function returns an attribute __metadata decorator
@Reflect.metadata
, its purpose is to instantiate the service
metadata 'design:type'
stored reflect.metadata
time, so we need to rely on injection, by Reflect.getMetadata
Get the corresponding service
, and instantiate and assign it to the required attribute.
@Inject
compiled code:
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
// 由于__decorate是从右到左执行,因此, defineMetaData 会优先执行。
__decorate([
Inject,
__metadata("design:type", A) // 作用等同于 Reflect.metadata("design:type", A)
], B.prototype, "a", void 0);
executes the following code by default:
Reflect.defineMetadata("design:type", A, B.prototype, 'a');
Inject
function needs to do is metadata
and construct an instance object to assign it to the currently decorated attribute
function Inject(target: any, key: string){
target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
However, there is a problem with this dependency injection method:
Since the
Inject function will be executed during the code compilation phase,
B.prototype
will be modified during the code compilation phase, which violates(avoid directly modifying the class, but should expand on the class)
So how to solve this problem, we can learn from the idea ofTypeDI
typedi
typedi is a dependency injection tool that supports TypeScript and JavaScript
Typedi's dependency injection idea is similar, but one more is maintained,container
1. metadata
Before understanding its
container
, we need to understand the 06130302c9fe05 defined inmetadata
. Here we will focus on the more important attributes that I know.id: the unique identifier of the service
type: save service constructor
value: the instantiated object corresponding to the cache service
const newMetadata: ServiceMetadata<T> = {
id: ((serviceOptions as any).id || (serviceOptions as any).type) as ServiceIdentifier, // service的唯一标识
type: (serviceOptions as ServiceMetadata<T>).type || null, // service 构造函数
value: (serviceOptions as ServiceMetadata<T>).value || EMPTY_VALUE, // 缓存service对应的实例化对象
};
2. Container function
function ContainerInstance() {
this.metadataMap = new Map(); //保存metadata映射关系,作用类似于Refect.metadata
this.handlers = []; // 事件待处理队列
get(){}; // 获取依赖注入后的实例化对象
...
}
this. metadataMap-
@service
will save theservice constructor in the form of metadata to
this.metadataMap
.- Cache instantiated objects to ensure singleton;
this.handlers-
@inject
will push theobject,
target, and
behavior of the dependency injection operation into the handlers array to be processed in the form of object.
Save the mapping relationship between the
constructor and the
static type and
attributes.
{ object: target, // 当前等待挂载的类的原型对象 propertyName: propertyName, // 目标属性值 index: index, value: function (containerInstance) { // 行为 var identifier = Reflect.getMetadata('design:type', target, propertyName) return containerInstance.get(identifier); } }
@inject
the object into a pending array of handlers waiting to be executed, and executes the value function and modifies the propertyName when the corresponding service is needed.if (handler.propertyName) { instance[handler.propertyName] = handler.value(this); }
get-Object instantiation operations and dependency injection operations
- Avoid directly modifying the class, but expand the properties of its instantiated objects;
Related conclusions
typedi
instantiation operation is not performed immediately, but ahandlers
pending array, waitingContainer.get(B)
, B is instantiated on first, and then fromhandlers
taken to be treated corresponding arrayvalue and performs the function of a modified example of the properties object Value so that it does not affect Class B itself
- After the attribute value of the instance is modified, it will be cached to
metadata.value
(typedi's singleton service feature).
Related information can be viewed:
https://stackoverflow.com/questions/55684776/typedi-inject-doesnt-work-but-container-get-does
new B().say() // 将会输出sayHello is undefined
Container.get(B).say() // hello word
Implement a simple version of DI Container
The code here depends on TS
and does not support JS environment
interface Handles {
target: any
key: string,
value: any
}
interface Con {
handles: Handles [] // handlers待处理数组
services: any[] // service数组,保存已实例化的对象
get<T>(service: new () => T) : T // 依赖注入并返回实例化对象
findService<T>(service: new () => T) : T // 检查缓存
has<T>(service: new () => T) : boolean // 判断服务是否已经注册
}
var container: Con = {
handles: [], // handlers待处理数组
services: [], // service数组,保存已实例化的对象
get(service){
let res: any = this.findService(service)
if(res){
return res
}
res = new service()
this.services.push(res)
this.handles.forEach(handle=>{
if(handle.target !== service.prototype){
return
}
res[handle.key] = handle.value
})
return res
},
findService(service){
return this.services.find(instance => instance instanceof service)
},
// service是否已被注册
has(service){
return !!this.findService(service)
}
}
function Inject(target: any, key: string){
const service = Reflect.getMetadata('design:type',target,key)
// 将实例化赋值操作缓存到handles数组
container.handles.push({
target,
key,
value: new service()
})
// target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
class A {
sayA(name: string){
console.log('i am '+ name)
}
}
class B {
@Inject
a: A
sayB(name: string){
this.a.sayA(name)
}
}
class C{
@Inject
c: A
sayC(name: string){
this.c.sayA(name)
}
}
// new B().sayB(). // Cannot read property 'sayA' of undefined
container.get(B).sayB('B')
container.get(C).sayC('C')
wonderful · 16130302ca03e1
[A front-end who doesn’t understand physics is not a good game developer (1)
[3D performance optimization | talk about glTF file compression]
[Jingdong Shopping Mini Program | Taro3 Project Subcontracting Practice]
Welcome to follow the blog of Lab: 16130302ca048c aotu.io
Or follow the AOTULabs official account (AOTULabs) and push articles from time to time.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。