Nest是Node.js的服务器端框架。它最著名的是它的IOC(inverseofcontrol)机制,即不需要手动创建实例,框架会自动扫描需要加载的类,并将它们的实例创建到容器中,并在实例化时根据类的构造函数参数自动注入依赖。一般是这样使用的:比如在入口Module中导入一个Module:import{Module}from'@nestjs/common';从'./cats/cats.module'导入{CatsModule};@Module({imports:[CatsModule],})exportclassAppModule{}然后这个模块的Module会声明Controller和Service:import{Module}from'@nestjs/common';import{CatsController}from'./cats.controller';import{CatsService}from'./cats.service';@Module({controllers:[CatsController],providers:[CatsService],})exportclassCatsModule{}Controller是声明对应的处理逻辑url:import{Body,Controller,Get,Param,Post}from'@nestjs/common';import{CatsService}from'./cats.service';import{CreateCatDto}from'./dto/create-cat.dto';@Controller('cats')exportclassCatsController{constructor(privatereadonlycatsService:CatsService){}@Post()asynccreate(@Body()createCatDto:CreateCatDto){这个。猫服务。创建(createCatDto);}@Get()asyncfindAll():Promise{returnthis.catsService.findAll();}}这个CatsController的构造函数声明了对CatsService的依赖:CatsService可以操作数据库进行增删改查。这是一个简单的实现:import{Injectable}from'@nestjs/common';import{Cat}from'./interfaces/cat.interface';@Injectable()exportclassCatsService{privatereadonlycats:Cat[]=[];创建(猫:猫){this.cats.push(猫);}findAll():Cat[]{返回this.cats;}}然后在入口调用create运行整个nest应用:import{NestFactory}from'@nestjs/core';从'./app.module'导入{AppModule};asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);等待app.listen(3000);}bootstrap();然后浏览器访问我们写的controller对应的url,打断点:你会发现controller的实例已经创建好了,服务也已经注入了。这就是依赖注入的意义所在。这种机制称为IOC(InversionofControl),也称为依赖注入。好处很明显,就是只需要声明依赖,不需要自己创建对象。该框架将扫描声明并自动创建和注入依赖项。Java中最流行的Spring框架就是IOC的实现,Nest也是这样一个实现了IOC机制的Node.js后端框架。不知道大家有没有觉得很神奇,只是通过装饰器声明一下,然后启动Nest应用。这个时候对象就创建好了,依赖注入了。那么它是如何实现的呢?如果这样去想它的实现原理,你可能想不通,因为缺少了一些前置知识。即实现Nest的一些核心API:Reflect的元数据的API。有ReflectMetadata的同学会说,我对Reflect的API很熟悉,就是一些操作对象的属性、方法、构造函数的API:比如Reflect.get就是获取对象的属性值。Reflect.set是设置对象的属性值。Reflect.has是判断对象属性是否存在。Reflect.apply是调用一个方法,传入对象和参数。Reflect.construct是使用构造函数创建对象实例,并传入构造函数参数。这些api可以在MDN文档中找到,因为它们已经是es标准,并且已经被很多浏览器实现了。但是实现Nest的api还没有进入标准,还在草案阶段,就是metadataapi:它有这些api:Reflect.defineMetadata(metadataKey,metadataValue,target);Reflect.defineMetadata(metadataKey,metadataValue,target,propertyKey);letresult=Reflect.getMetadata(metadataKey,target);letresult=Reflect.getMetadata(metadataKey,target,propertyKey);Reflect.defineMetadata和Reflect.getMetadata用于设置并分别获取一个类的元数据,如果最后传入了属性名,也可以为一个属性单独设置元数据。元数据存在于何处?它存在于类或对象上。如果将元数据添加到类或类的静态属性中,它将存储在类上。如果将元数据添加到实例属性,它将存储在对象上。使用像[[metadata]]这样的键来保存。什么用途?看上面的api,什么也看不出来,不过也支持装饰器的使用:@Reflect.metadata(metadataKey,metadataValue)classC{@Reflect.metadata(metadataKey,metadataValue)method(){}}Reflect.当然元数据装饰器还可以再封装一层:paramtypes",types);}functionReturnType(type){returnReflect.metadata("design:returntype",type);}@ParamTypes(String,Number)classGuang{constructor(text,i){}@Type(String)获取名称(){返回“文本”;}@Type(Function)@ParamTypes(Number,Number)@ReturnType(Number)add(x,y){returnx+y;}}然后我们可以通过Reflectmetadataapi或者这个类和对象的元数据:letobj=newGuang("a",1);letparamTypes=Reflect.getMetadata("design:paramtypes",inst,"add");//[Number,Number]这里我们使用Reflect.getMetadata的api来获取add方法的参数类型。看到这里,你明白nest的原理了吗?我们再看看nest的源码:上面是@Module装饰器的实现,调用了Reflect.defineMetadata为这个类添加了一些元数据。所以当我们这样使用它时:import{Module}from'@nestjs/common';import{CatsController}from'./cats.controller';import{CatsService}from'./cats.service';@Module({controllers:[CatsController],providers:[CatsService],})exportclassCatsModule{}实际上是将控制器的元数据和提供者的元数据添加到CatsModule中。后面创建IOC容器的时候,会把这些元数据取出来处理:而@Controller和@Injectable的装饰器也是这样实现的:看到这里,是不是想明白nest的实现原理了?其实就是通过装饰器给类或者对象加上元数据,然后在初始化的时候把元数据取出来,分析依赖关系,然后创建对应的实例对象。所以nest实现的核心就是Reflectmetadata的api。当然metadataAPI还在草案阶段,所以需要使用polyfill包reflect-metadata。其实还是有疑问的。依赖扫描可以传递元数据数据,但是创建的对象需要知道构造函数的参数,这部分元数据数据现在没有添加:比如这个CatsController依赖CatsService,但是没有添加元数据:import{Body,Controller,Get,Param,Post}from'@nestjs/common';import{CatsService}from'./cats.service';import{CreateCatDto}from'./dto/create-cat.dto';@Controller('cats')exportclassCatsController{constructor(privatereadonlycatsService:CatsService){}@Post()asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}@Get()asyncfindAll():Promise{returnthis.catsService.findAll();}}这就不得不提TypeScript的优点了,TypeScript支持在编译时自动添加一些元数据数据:比如这段代码:import"reflect-metadata";classGuang{@Reflect.metadata("Name","Guangguang")publicsay(a:number):string{return'Comeonduck';}按理说我们只添加了一个元数据,生成的代码确实是这样的:但是,ts有一个编译选项叫emitDecoratorMetadata,开启后会自动添加一些元数据。打开后再试:会看到多了三个元数据:design:typeisFunction,很明显,这是描述装饰目标的元数据,这里是函数design:paramtypesis[Number],很容易理解,是的类型参数design:returntype是String,也很容易理解,就是返回值的类型。所以,只要开启这个编译选项,ts生成的代码就会自动添加一些元数据。然后在创建对象的时候,可以通过design:paramtypes获取到构造函数参数的类型,难道不知道注入依赖吗?因此,你会在嵌套源码中看到这样的代码:就是获取构造函数的参数类型。这个常量就是我们上面提到的那个:这就是为什么nest要用ts写的原因,因为它非常依赖emitDecoratorMetadata的编译选项。在你用cli生成的代码模板中也默认开启了这个编译选项:这就是nest的核心实现原理:通过装饰器为类或对象添加元数据,启用ts的emitDecoratorMetadata自动添加类型相关的元数据,然后运行时使用这些元数据实现依赖扫描,对象创建等功能。总结Nest是Node.js的后端框架。它的核心是IOC容器,自动扫描依赖,创建实例对象,自动注入依赖。要了解它的实现原理,首先需要学习Reflectmetadata的api:这是给类或对象添加元数据。元数据可以通过Reflect.metadata添加到一个类或对象中,在后面使用该类或对象时,可以通过Reflect.getMetadata获取。Nest的Controller、Module、Service等所有的装饰器都是通过Reflect.meatdata给类或者对象添加元数据,然后在初始化时取出来进行依赖扫描,实例化后放入IOC容器中。实例化对象还需要构造函数参数的类型。启用ts的emitDecoratorMetadata的编译选项后,ts会自动添加一些元数据,即design:type、design:paramtypes、design:returntype,分别代表被装饰目标的类型、参数的类型、类型的返回值。当然,reflectmetadataAPI还在draft阶段,需要将reflectmetadatapackage作为polyfill引入。一系列嵌套装饰器为类和对象添加元数据,然后在依赖扫描和依赖注入时取出元数据进行一些处理。了解了元数据后,nest的实现原理就很容易理解了。