<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Nest 是 Node.js 的伺服器端框架,它最出名的就是 IOC(inverse of control) 機制了,也就是不需要手動建立範例,框架會自動掃描需要載入的類,並建立他們的範例放到容器裡,範例化時還會根據該類的構造器引數自動注入依賴。
它一般是這樣用的:
比如入口 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 裡就可以去運算元據庫進行增刪改查了。
這裡簡單實現一下:
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 的 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 的原理了呢?
上面就是 @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原理的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45