本文是根据自己的理解来输出的,目的是交流学习,如有不妥之处,希望大家指出。DIDI—DependencyInjection,即“依赖注入”:对象之间的依赖关系由容器在运行时决定。形象地说,容器是动态地将一个对象注入到对象属性中。依赖注入的目的不是给软件系统带来更多的功能,而是增加对象重用的频率,为系统构建一个灵活可扩展的框架。如何使用先看常见的依赖注入(DI)方法:functionInject(target:any,key:string){target[key]=new(Reflect.getMetadata('design:type',target,key))()}classA{sayHello(){console.log('hello')}}classB{@Inject//编译后相当于执行@Reflect.metadata("design:type",A)a:Asay(){this.a.sayHello()//不用再实例化A类}}newB().say()//hello原理分析TS在编译装饰器A属性时会通过执行__metadata函数返回更多装饰器@Reflect.metadata,它的作用是将需要实例化的服务和metadata'design:type'一起存储到reflect.metadata中,方便我们需要依赖注入的时候通过Reflect.getMetadata获取对应的服务,以及实例化并为所需的属性赋值。@Inject编译代码:var__metadata=(this&&this.__metadata)||function(k,v){if(typeofReflect==="object"&&typeofReflect.metadata==="function")返回反射。metadata(k,v);};//由于__decorate是从右到左执行的,所以defineMetaData会先执行。__decorate([Inject,__metadata("design:type",A)//效果等同于Reflect.metadata("design:type",A)],B.prototype,"a",void0);即默认执行以下代码:Reflect.defineMetadata("design:type",A,B.prototype,'a');Inject函数需要做的是从元数据中获取对应的构造函数,构造一个实例对象赋值给当前装饰的属性函数Inject(target:any,key:string){target[key]=new(Reflect.getMetadata('design:type',target,key))()}但是这种依赖注入方式有一个问题:由于Inject函数是在代码阶段编译的时候才会执行,这会导致B.prototype被修改在代码编译阶段,违反了六大设计原则中的开闭原则(避免直接修改类,而应该在类上进行扩展)。那么如何解决这个问题,我们可以借鉴一下TypeDI的思路。typeditypedi是一个依赖注入工具,支持TypeScript和JavaScript。typedi的依赖注入思路类似,只是多了一个container1。元数据被维护。在了解它的容器之前,我们需要先了解typedi中定义的元数据。这里我们将重点放在我所了解的一些比较重要的属性上去理解。id:服务唯一标识类型:保存服务构造函数值:缓存服务对应的实例化对象constnewMetadata:ServiceMetadata={id:((serviceOptionsasany).id||(serviceOptionsasany).type)asServiceIdentifier,//服务类型的唯一标识符:(serviceOptionsasServiceMetadata).type||null,//服务构造函数值:(serviceOptionsasServiceMetadata).value||EMPTY_VALUE,//缓存服务对应的实例化对象};2.容器函数ContainerInstance()的作用{this.metadataMap=newMap();//保存元数据映射关系,功能类似Refect.metadatathis.handlers=[];//事件等待队列get(){};//获取依赖注入后的实例化对象...}this.metadataMap-@service会将服务构造函数以元数据的形式保存在this.metadataMap中。缓存实例化对象,保证单实例;this.handlers-@inject会将依赖注入操作的对象、目标、行为以对象的形式推送到handlers数组中进行处理。保存构造函数与静态类型和属性的映射关系。{object:target,//当前等待挂载类的原型对象propertyName:propertyName,//目标属性值index:index,value:function(containerInstance){//行为varidentifier=Reflect.getMetadata('design:type',target,propertyName)returncontainerInstance.get(identifier);}}@inject将对象压入一个等待执行的handlers数组中,在需要使用对应的服务时执行value函数并修改propertyName。if(handler.propertyName){instance[handler.propertyName]=handler.value(this);}get-对象实例化操作和依赖注入操作避免直接修改类,而是扩展实例化对象的属性;相关结论typedi中的实例化操作不会立即执行,而是在一个pendingarrayofhandlers中,等待Container.get(B),先实例化B,然后从pendinghandlers数组中取出对应的valuefunction执行修改实例化对象的属性值不会受到影响。B类实例的属性值修改后,会缓存到metadata.value中(typedi的单例服务特性)。相关资料可以查看:https://stackoverflow.com/questions/55684776/typedi-inject-doesnt-work-but-container-get-doesnewB().say()//会输出sayHelloisundefinedContainer.get(B).say()//helloword实现了一个简单版的DIContainer,代码依赖TS,不支持JS环境interfaceHandles{target:anykey:string,value:any}interfaceCon{handles:Handles[]//handlers待处理数组services:any[]//服务数组,保存实例化对象get(service:new()=>T):T//依赖注入并返回实例化对象findService(service:new()=>T):T//检查缓存是否有(service:new()=>T):boolean//判断服务是否已经注册}varcontainer:Con={handles:[],//待处理的处理程序数组services:[],//服务数组,保存实例化对象get(service){letres:any=this.findService(service)if(res){returnres}res=新服务()this.services.push(res)this.handles.forEach(handle=>{if(handle.target!==service.prototype){返回}res[handle.key]=handle.value})returnres},findService(service){returnthis.services.find(instance=>instanceinstanceofservice)},//服务是否已经注册has(service){return!!this.findService(service)}}functionInject(target:any,key:string){constservice=Reflect.getMetadata('design:type',target,key)//缓存实例化赋值操作到句柄数组container.handles.push({target,key,value:newservice()})//target[key]=new(Reflect.getMetadata('design:type',target,key))()}classA{sayA(name:string){console.log('iam'+name)}}classB{@Injecta:AsayB(name:string){this.a.sayA(name)}}classC{@Injectc:AsayC(name:string){this.c.sayA(name)}}//newB().sayB()。//Cannotreadproperty'sayA'ofundefinedcontainer.get(B).sayB('B')container.get(C).sayC('C')·过去的精彩·【一个做前端的不懂物理不是好的游戏开发者(一)——物理导论引擎基础][3D性能优化|浅谈glTF文件压缩】【京东购物小程序|定期推送文章