正文

Nest 是 Node.js 的服务端框架,它最出名的就是 IOC(inverse of control) 机制了,也就是不需要手动创建实例,框架会自动扫描需要加载的类,并创建他们的实例放到容器里,实例化时还会根据该类的构造器参数自动注入依赖。

它一般是这样用的:

入口Module 引入模块

比如入口 Module 里引入某个模块的 Module:

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
  imports: [CatsModule],
})
export class AppModule {}

然后这个模块的 Module 里会声明 Controller 和 Service:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

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')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}
  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

这个 CatsController 的构造器声明了对 CatsService 的依赖:

然后 CatsService 里就可以去操作数据库进行增删改查了。

CatsService操作数据库

这里简单实现一下:

import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];
  create(cat: Cat) {
    this.cats.push(cat);
  }
  findAll(): Cat[] {
    return this.cats;
  }
}

之后在入口处调用 create 把整个 nest 应用跑起来:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

然后浏览器访问下我们写的那个 controller 对应的 url,打个断点:

你会发现 controller 的实例已经创建了,而且 service 也给注入了。这就是依赖注入的含义。

这种机制就叫做 IOC(控制反转),也叫依赖注入,好处是显而易见的,就是只需要声明依赖关系,不需要自己创建对象,框架会扫描声明然后自动创建并注入依赖。

Java 里最流行的 Spring 框架就是 IOC 的实现,而 Nest 也是这样一个实现了 IOC 机制的 Node.js 的后端框架。

不知道大家有没有感觉很神奇,只是通过装饰器声明了一下,然后启动 Nest 应用,这时候对象就给创建好了,依赖也给注入了。

那它是怎么实现的呢?

大家如果就这样去思考它的实现原理,还真不一定能想出来,因为缺少了一些前置知识。也就是实现 Nest 最核心的一些 api: Reflect 的 metadata 的 api。

Reflect Metadata

有的同学会说,Reflect 的 api 我很熟呀,就是操作对象的属性、方法、构造器的一些 api:

比如 Reflect.get 是获取对象属性值

Reflect.set 是设置对象属性值

Reflect.has 是判断对象属性是否存在

Reflect.apply 是调用某个方法,传入对象和参数

Reflect.construct 是用构造器创建对象实例,传入构造器参数

这些 api 在 MDN 文档里可以查到,因为它们都已经是 es 标准了,也被很多浏览器实现了。

但是实现 Nest 用到的 api 还没有进入标准,还在草案阶段,也就是 metadata 的 api:

它有这些 api:

Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);

Reflect.defineMetadata 和 Reflect.getMetadata 分别用于设置和获取某个类的元数据,如果最后传入了属性名,还可以单独为某个属性设置元数据。

那元数据存在哪呢?

存在类或者对象上呀,如果给类或者类的静态属性添加元数据,那就保存在类上,如果给实例属性添加元数据,那就保存在对象上,用类似 [[metadata]] 的 key 来存的。

这有啥用呢?

看上面的 api 确实看不出啥来,但它也支持装饰器的方式使用:

@Reflect.metadata(metadataKey, metadataValue)
class C {
  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}

Reflect.metadata 装饰器当然也可以再封装一层:

function Type(type) {
    return Reflect.metadata("design:type", type);
}
function ParamTypes(...types) {
    return Reflect.metadata("design:paramtypes", types);
}
function ReturnType(type) {
    return Reflect.metadata("design:returntype", type);
}
@ParamTypes(String, Number)
class Guang {
  constructor(text, i) {
  }
  @Type(String)
  get name() { return "text"; }
  @Type(Function)
  @ParamTypes(Number, Number)
  @ReturnType(Number)
  add(x, y) {
    return x   y;
  }
}

然后我们就可以通过 Reflect metadata 的 api 或者这个类和对象的元数据了:

let obj = new Guang("a", 1);
let paramTypes = Reflect.getMetadata("design:paramtypes", obj, "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],
})
export class CatsModule {}

其实就是给 CatsModule 添加了 controllers 的元数据和 providers 的元数据。

后面创建 IOC 容器的时候就会取出这些元数据来处理:

而且 @Controller 和 @Injectable 的装饰器也是这样实现的:

看到这里,大家是否想明白 nest 的实现原理了呢?

其实就是通过装饰器给 class 或者对象添加元数据,然后初始化的时候取出这些元数据,进行依赖的分析,然后创建对应的实例对象就可以了。

所以说,nest 实现的核心就是 Reflect metadata 的 api。

当然,现在 metadata 的 api 还在草案阶段,需要使用 reflect-metadata 这个 polyfill 包才行。

其实还有一个疑问,依赖的扫描可以通过 metadata 数据,但是创建的对象需要知道构造器的参数,现在并没有添加这部分 metadata 数据呀:

比如这个 CatsController 依赖了 CatsService,但是并没有添加 metadata 呀:

import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}
  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

这就不得不提到 TypeScript 的优势了,TypeScript 支持编译时自动添加一些 metadata 数据:

比如这段代码:

import "reflect-metadata";
class Guang {
  @Reflect.metadata("名字", "光光")
  public say(a: number): string {
    return '加油鸭';
  }
}

按理说我们只添加了一个元数据,生成的代码也确实是这样的:

但是呢,ts 有一个编译选项叫做 emitDecoratorMetadata,开启它就会自动添加一些元数据。

开启之后再试一下:

你会看到多了三个元数据:

design:type 是 Function,很明显,这个是描述装饰目标的元数据,这里装饰的是函数

design:paramtypes 是 [Number],很容易理解,就是参数的类型

design:returntype 是 String,也很容易理解,就是返回值的类型

所以说,只要开启了这个编译选项,ts 生成的代码会自动添加一些元数据。

然后创建对象的时候就可以通过 design:paramtypes 来拿到构造器参数的类型了,那不就知道怎么注入依赖了么?

所以,nest 源码里你会看到这样的代码:

就是获取构造器的参数类型的。这个常量就是我们上面说的那个:

这也是为什么 nest 会用 ts 来写,因为它很依赖这个 emitDecoratorMetadata 的编译选项。

你用 cli 生成的代码模版里也都默认开启了这个编译选项:

这就是 nest 的核心实现原理:通过装饰器给 class 或者对象添加 metadata,并且开启 ts 的 emitDecoratorMetadata 来自动添加类型相关的 metadata,然后运行的时候通过这些元数据来实现依赖的扫描,对象的创建等等功能。

总结

Nest 是 Node.js 的后端框架,他的核心就是 IOC 容器,也就是自动扫描依赖,创建实例对象并且自动依赖注入。

要搞懂它的实现原理,需要先学习 Reflect metadata 的 api:

这个是给类或者对象添加 metadata 的。可以通过 Reflect.metadata 给类或者对象添加元数据,之后用到这个类或者对象的时候,可以通过 Reflect.getMetadata 把它们取出来。

Nest 的 Controller、Module、Service 等等所有的装饰器都是通过 Reflect.meatdata 给类或对象添加元数据的,然后初始化的时候取出来做依赖的扫描,实例化后放到 IOC 容器里。

实例化对象还需要构造器参数的类型,这个开启 ts 的 emitDecoratorMetadata 的编译选项之后, ts 就会自动添加一些元数据,也就是 design:type、design:paramtypes、design:returntype 这三个,分别代表被装饰的目标的类型、参数的类型、返回值的类型。

当然,reflect metadata 的 api 还在草案阶段,需要引入 refelect metadata 的包做 polyfill。

nest 的一系列装饰器就是给 class 和对象添加 metadata 的,然后依赖扫描和依赖注入的时候就把 metadata 取出来做一些处理。

理解了 metadata,nest 的实现原理就很容易搞懂了。

以上就是从reflect metadata理解Nest实现原理的详细内容,更多关于reflect metadata Nest原理的资料请关注Devmax其它相关文章!

从reflect metadata理解Nest实现原理的更多相关文章

  1. Python reflect单例模式反射各个函数

    这篇文章主要介绍了Python reflect单例模式反射各个函数,文章围绕主题展开详细的内容介绍,具有一定的参考价值需要的小伙伴可以参考一下

  2. vue3响应式Proxy与Reflect的理解及基本使用实例详解

    这篇文章主要为大家介绍了vue3响应式Proxy与Reflect的理解及基本使用实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  3. 从reflect metadata理解Nest实现原理

    这篇文章主要为大家介绍了从reflect metadata理解Nest实现原理示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. JS 中Proxy代理和 Reflect反射方法示例详解

    这篇文章主要为大家介绍了JS 中Proxy代理和 Reflect反射方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  5. js中为什么Proxy一定要配合Reflect使用

    大多数开发者都了解这两个 Es6 中的新增内置模块,可是你也许并不清楚为什么 Proxy 一定要配合 Reflect 使用,本文主要介绍了js中为什么Proxy一定要配合Reflect使用,感兴趣的可以了解一下

  6. 解决Android Studio Gradle Metadata特别慢的问题

    这篇文章主要介绍了解决Android Studio Gradle Metadata特别慢的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  7. ubuntu – 我的Nest能否测试是否存在本地设备,以确定我是否在家?

    实际上,我使用nestAPI想出了如何准确地做到这一点.它完美地运作!我已经创建了一对Python脚本,/usr/bin/nest-home和/usr/bin/nest-away,它们是我上传到Ubuntu的uhome软件包的一部分.还有/usr/bin/uhome脚本,它处理我的第一个问题–读取IP/MAC地址的输入列表,迭代它们以查看是否有任何可ping的,并根据结果,标记我们的家或者离开.与nest交谈的真正魔力在于3个后续的API调用:>登录并建立身份验证令牌>检索结构ID>更新结构的远离布尔值完

  8. d3.js – 复杂d3.nest()操作

    我有一个数组,如下所示:我想使用d3.nest()或者甚至只是标准的javascript来将这些数据转换成一个嵌套的数据结构,我可以使用d3.partition.具体来说,我想创建这里找到的flare.jsondataformat.要使用d3.nest()创建的json对象的级别对应于数组中的索引位置.请注意,在上述示例数据中,1位于所有子阵列的第一个位置;因此,它是在树的根.在阵列中的下一个位置

  9. c# – 使用NEST 2.x创建具有多字段映射语法的索引

    解决方法据我所知,你没有任何复杂的类型,你正在尝试映射.因此,您可以轻松使用nesT属性来映射对象.看一下这个:在此声明之后,要在elasticsearch中创建此映射,您需要进行类似于以下的调用:使用AutoMap()调用nesT将从POCO中读取您的属性并相应地创建映射请求.另请参见here中的“基于属性的映射”部分.干杯!

  10. Jquery Mobile:强制刷新内容

    我有一个大问题:我有一个列表视图,每个项目链接到页面#concorsi。当我点击链接时,URL变为#concorsi?numero=1,因为我从JSON获取表单1。每个输入都使用jQueryMobile类可视化,但如果我回到同一个链接后,代码不会刷新。标题很好地可视化,但内容没有。如何强制刷新div内容?

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部