望文生義即「反向控制」,它是一種設計思想,大體意思就是把對象控制的全部權交給別人(容器)html
看如下代碼,便是自身應用程序主動去獲取依賴對象,而且本身建立對象前端
// 常見的依賴 import {A} from './A'; import {B} from './B'; class C { constructor() { this.a = new A(); this.b = new B(this.a); } }複製代碼
咱們看上面的代碼發現A被B和C依賴,這種依賴關係隨這着應用的增大,愈來愈複雜,耦合度也愈來愈高。因此有人提出了IOC理念,解決對象間的解耦。git
提供了一個container容器來管理,它是依賴注入設計模式的體現,如下代碼就使得C和A、B沒有的強耦合關係,直接經過container容器來管控github
// 使用 IoC import {Container} from 'injection'; import {A} from './A'; import {B} from './B'; const container = new Container(); container.bind(A); container.bind(B); class C { A:B constructor() { this.a = container.get('a'); this.b = container.get('b'); } }複製代碼
如下是實現IOC容器的最簡僞代碼:web
class Container { //存放每一個文件暴露的類和類名 classObjs = {} get(Module) { let obj = new Module() const properties = Object.getOwnPropertyNames(obj); for(const p of properties) { if(!obj[p]) { if(!this.classObjs[p]) { obj[p] = this.get(this.classObjs[p]) } } } return obj } }複製代碼
可是業界實現的方式主要是經過裝飾器 decorator 和 reflect-metadata來實現的,接下來就聊聊這二者是如何配合實現依賴注入(DI)的。注: DI是IOC的一種實現方式。spring
裝飾器是一種函數,是在代碼編譯的時候對類的行爲進行修改,好比:typescript
function helloWord(target: any) { console.log('hello Word!'); } @helloWord class HelloWordClass { } //tsc編譯後 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; function helloWord(target) { console.log('hello Word!'); } let HelloWordClass = class HelloWordClass { }; HelloWordClass = __decorate([ helloWord ], HelloWordClass);複製代碼
裝飾器主要有這幾種: 類裝飾器,方法、屬性裝飾器、參數裝飾器。當裝飾器運行的時候,函數會接收三個參數:target, key ,descriptor, 修飾不一樣的類型 target、key、descriptor 有所不一樣,詳細請看文檔後端
Reflect Metadata 是 ES7 的一個提案, 它本質是一個WeakMap對象,數據結構以下:設計模式
WeakMap {
target: Map {
propertyKey: Map {
metadataKey: metadataValue
}
}
}複製代碼
因此 Reflect.defineMetadata(metadataKey, metadataValue, target[, propertyKey]) 簡化版實現以下:bash
const weakMap = new WeakMap()
const defineMetadata = (metadataKey, metadataValue, target, propertyKey) => {
const metadataMap = new Map();
metadataMap.set(metadataKey, metadataValue)
const targetMap = new Map();
targetMap.set(propertyKey, metadataMap)
weakMap.set(target, targetMap)
}複製代碼
Reflect-Metadata通常結合着decorators一塊兒用,爲類和類屬性添加元數據。
基於Typescript的依賴注入就是經過這二者結合來實現的。
type Constructor<T = any> = new (...args: any[]) => T; const Injectable = (): ClassDecorator => target => {}; class OtherService { a = 1; } @Injectable() class TestService { constructor(public readonly otherService: OtherService) {} testMethod() { console.log(this.otherService.a); } } const Factory = <T>(target: Constructor<T>): T => { // 獲取全部注入的服務 const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService] const args = providers.map((provider: Constructor) => new provider()); return new target(...args); }; Factory(TestService).testMethod(); // 1複製代碼
經過如下編譯後的代碼發現,Typescriopt 經過__decorate將OtherService注入到了TestService類裏面,而後經過new target(...args)將OtherService賦值到實例屬性上
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; const Injectable = () => target => { }; class OtherService { constructor() { this.a = 1; } } let TestService = class TestService { constructor(otherService) { this.otherService = otherService; } testMethod() { console.log(this.otherService.a); } }; TestService = __decorate([ Injectable(), __metadata("design:paramtypes", [OtherService]) ], TestService); const Factory = (target) => { // 獲取全部注入的服務 const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService] const args = providers.map((provider) => new provider()); return new target(...args); }; Factory(TestService).testMethod(); // 1複製代碼
咱們在後端的框架裏看到不少這種註解的寫法,其實也是這樣實現的
@Controller('/test') class SomeClass { @Get('/a') someGetMethod() { return 'hello world'; } @Post('/b') somePostMethod() {} }複製代碼
首先咱們先利用自定義metaKey生成裝飾器
const METHOD_METADATA = 'method'; const PATH_METADATA = 'path'; const Controller = (path: string): ClassDecorator => { return target => { Reflect.defineMetadata(PATH_METADATA, path, target); } } const createMappingDecorator = (method: string) => (path: string): MethodDecorator => { return (target, key, descriptor) => { Reflect.defineMetadata(PATH_METADATA, path, descriptor.value); Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value); } } const Get = createMappingDecorator('GET'); const Post = createMappingDecorator('POST');複製代碼
而後在裝飾器裏經過Reflect.getMetadata獲取到剛剛存入(Reflect.defineMetadata)的元數據,
最後在將這些元數據重組生成一個map數據結構。
function mapRoute(instance: Object) { const prototype = Object.getPrototypeOf(instance); // 篩選出類的 methodName const methodsNames = Object.getOwnPropertyNames(prototype) .filter(item => !isConstructor(item) && isFunction(prototype[item])); return methodsNames.map(methodName => { const fn = prototype[methodName]; // 取出定義的 metadata const route = Reflect.getMetadata(PATH_METADATA, fn); const method = Reflect.getMetadata(METHOD_METADATA, fn); return { route, method, fn, methodName } }) };複製代碼
有了以上的方法,經過如下調用,再將生成的Routes綁定到koa上就ok了
Reflect.getMetadata(PATH_METADATA, SomeClass); // '/test' mapRoute(new SomeClass()); /** * [{ * route: '/a', * method: 'GET', * fn: someGetMethod() { ... }, * methodName: 'someGetMethod' * },{ * route: '/b', * method: 'POST', * fn: somePostMethod() { ... }, * methodName: 'somePostMethod' * }] * */複製代碼
最後你會發現不少spring後端框架的一些思想,其實都慢慢的被運用到了前端,好比如今比較流行的web框架Midway
參考