以前写过的一篇关于《前端IOC
的简单实践》,基于本人是一个前端,设计模式的基础知识都不完备,因此内容不是太严谨,正在学习中! 文章中提到了一个关键词:依赖注入。前端
有小伙伴跟我提说在真实项目中如何使用的,我知道 angular
就是借鉴 spring
的 ioc
,奈何我没有用过,下面呢就来讲说我以前在nodejs
项目上的一些实践。node
去年,我贴了一个 nodejs
的简易web框架-- lenneth,基于 koa2
搞的,简单说就是用装饰器模仿 spring
来写 nodejs
的注解(说注解习惯了,就不说装饰器了),看下示例:git
import {
Controller,
Autowired,
Post,
Get,
RequestBody,
PathVariable,
Response,
TResponse,
UseBefore,
Description
} from "lenneth";
import { UserService } from "../services";
import { IUserInfo } from "../interface";
import { UserAuth, RuleAuth } from "../middleware";
@Controller("/user")
export class UserController {
@Autowired()
userService: UserService;
@Post("/add")
@Description("添加会员")
@UseBefore(UserAuth, RuleAuth)
async addUser(
@RequestBody() user: IUserInfo,
@Response() response: TResponse
) {
response.body = this.userService.addUser(user);
}
@Get("/detail/:userId")
@UseBefore(UserAuth)
@Description("查询会员")
async getUser(
@PathVariable("userId") userId: string,
@Response() response: TResponse
) {
response.body = this.userService.getUserInfo(userId);
}
}
复制代码
看到这些注解,是否是很眼熟,就是从 spring
抄来的,具体介绍能够去项目里看看,下面来重点介绍实现 Autowired
注解的过程,也就是依赖注入的实践。es6
看上面的实例,这个项目依赖了一个 UserService
类,在这个 UserController
这个方法中会用到这个依赖类的某个方法。github
依赖注入:web
@Autowired()
userService: UserService;
复制代码
使用:spring
this.userService.addUser(user);
复制代码
来看下 Autowired
注解的实现:设计模式
import { Metadata } from "@common";
import { descriptorOf, getClassName } from "@utils";
/** * 注入service,类属性修饰器 * @param params 实例化参数 */
export const Autowired = (params: any = ""): Function => {
return (target: any, propertyKey: string) => {
// 获取该属性的类型
let typeClass = Metadata.getType(target, propertyKey);
const descriptor = descriptorOf(target, propertyKey) || {
writable: true,
configurable: true
};
// 实例化修饰类
descriptor.value = params ? new typeClass(params) : new typeClass();
Reflect.defineProperty(
(target && target.prototype) || target,
propertyKey,
descriptor
);
};
};
复制代码
解读这段实现以前,先引出了另外一个概念--反射,就是在运行时动态获取一个对象的一切信息,包括方法/属性等等,特色在于动态类型反推导。api
Reflect
是ES6新增的api,自己提供了很多静态方法,不过要使用还须要引入 reflect-metadata
这个库,为了使编译器在设计时将元数据序列化传给修饰器。bash
经过反射能得到系统提供的metadataKey
信息:
design:type
修饰目标对象的类型;design:paramtypes
修饰目标对象方法的参数类型;design:returntype
修饰目标对象方法返回值的类型;来看下案例:
import "reflect-metadata";
const validate = () => {
return (target: any, propertyKey: string) => {
// 修饰目标对象的类型
let type = Reflect.getMetadata("design:type", target, propertyKey);
// 修饰目标的参数类型
let paramTypes = Reflect.getMetadata(
"design:paramtypes",
target,
propertyKey
);
// 修饰目标的返回值类型
let returnType = Reflect.getMetadata(
"design:returntype",
target,
propertyKey
);
// 全部能经过反射获取的元数据类型key
let allKeys = Reflect.getMetadataKeys(target, propertyKey);
console.log("type", type);
console.log("paramTypes", paramTypes);
console.log("returnType", returnType);
console.log("allKeys", allKeys);
};
};
class Person {
private name: string;
@validate()
getInfo(tags: string): string {
return `your name is ${this.name}, tags is ${tags}`;
}
}
复制代码
控制台展现:
type function Function() { [native code] }
paramTypes [ [Function: String] ]
returnType function String() { [native code] }
allKeys [ 'design:returntype', 'design:paramtypes', 'design:type' ]
复制代码
特别注意:design:returntype
依赖于所修饰方法的是否显式定义类型了,若是没有定义类型,那就会默认返回 undefined
。
咱们也能够自定义 metadataKey
,即在相应的类上定义自定义的元数据。
const service = () => {
return (target: any) => {
// 自定义元数据,key 为 ServiceDecorator
Reflect.defineMetadata("ServiceDecorator", "your personal value", target);
};
};
@service()
class Person {
private name: string;
}
// 在合适的位置获取以前定义的元数据
// your personal value
console.log(Reflect.getMetadata("ServiceDecorator", Person));
复制代码
自此,有了这个知识,在看上面的 Autowired
代码是否是简单的多了。
Autowired
注解的本质是一个属性修饰器,主要是考虑到会有参数传入,因此就写了一个高阶函数。修饰器自己就不作介绍了,能够看下阮一峰老师的es6教程。
在方法内部,先获取了被修饰对象的类型,转换以下:
let typeClass = Reflect.getMetadata("design:type", target, propertyKey);
复制代码
这个 metadataKey
是系统提供的 design:type
,获取被修饰对象的类型。
@Autowired()
userService: UserService;
复制代码
那这个 typeClass
的值就是 UserService
。
// 获取指定对象属性的描述对象
const descriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey) || {
writable: true,
configurable: true
};
复制代码
这里就是获取 UserController
的 userService
属性的描述对象,那这个值有什么用呢?
Reflect.getOwnPropertyDescriptor
方法其实等同于 Object.getOwnPropertyDescriptor
,它会返回一个object:
{
value: "value",
writable: true,
enumerable: true,
configurable: true
}
复制代码
返回的四个字段中value
就是这个属性的值,咱们只要修改这个value
字段,就能够实现注入了。
descriptor.value = params ? new typeClass(params) : new typeClass();
Reflect.defineProperty(
(target && target.prototype) || target,
propertyKey,
descriptor
);
复制代码
因此,最后修改了这个属性的描述对象的值,使它指向了所返回类型的实例对象,再从新定义这个属性的描述对象,这样编译后,userService
这个被修饰的属性就是UserService
的实例对象,可以访问到UserService
内的属性方法了。
如此,就实现了 Autowired
注解的功能了。
完整示例:
const Autowired = (params: any = ""): Function => {
return (target: any, propertyKey: string) => {
// 获取该属性的类型
let typeClass = Reflect.getMetadata("design:type", target, propertyKey);
const descriptor = Reflect.getOwnPropertyDescriptor(
target,
propertyKey
) || {
writable: true,
configurable: true
};
// 实例化修饰类
descriptor.value = params ? new typeClass(params) : new typeClass();
Reflect.defineProperty(
(target && target.prototype) || target,
propertyKey,
descriptor
);
};
};
class UserService {
getUserById(id: string) {
return `user id is ${id}`;
}
}
class Person {
@Autowired()
private userService: UserService;
getUserInfo(id: string) {
console.log(this.userService.getUserById(id));
}
}
// user id is 12
console.log(new Person().getUserInfo("12"));
复制代码