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 of TypeDI

    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 in metadata . 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 the service constructor in the form of metadata to this.metadataMap .

    • Cache instantiated objects to ensure singleton;
  • this.handlers- @inject will push the object, 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 a handlers pending array, waiting Container.get(B) , B is instantiated on first, and then from handlers taken to be treated corresponding array value 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.


凹凸实验室
2.3k 声望5.5k 粉丝

凹凸实验室(Aotu.io,英文简称O2) 始建于2015年10月,是一个年轻基情的技术团队。