1. 装饰器
1. 装饰器的意义
装饰器是前端了不起的技术革命,弥补了只有后端语言才有 AOP(类似装饰器)的短板,学习装饰器好处有:
- 较大提升前端架构思维和前端架构能力,装饰器底层蕴含的拦截器思想在Java Spring, Nest.js框架,python各种后端语言中都有广泛的应用,而拦截器展示的就是一种架构思维,通过学习装饰器能扩大技术视野,是作为一名前端架构师以及更高职位必会技能。
- Nestjs 等相对新型的 Nodejs 框架大量运用了 TS 装饰器,例如: @Controller @Service @Get @Post
在面试中,如果告诉面试官,你精通装饰器,这也能成为你的大加分项,因为公司更需架构思维能力强的前端工程师,因为具有架构思维的前端开发人员在大中项目中一定能写出扩展性更好的代码。
2. 装饰器的定义和分类
:::info 【定义】装饰器就是一个方法,可以注入到类、方法、属性、参数和对象上,拓展其功能。 ::: :::success 装饰器要解决的问题
装饰器就是解决在不修改原来类、方法、属性和参数的时候,为其添加额外的功能。比如:为整个项目的所有业务类的所有方法都增加日志信息,如果一个个增加,那就要修增加大量的语句。假如日后日志文件格式发生了变化,也还需要修改300次。如果有了装饰器,只需要修改一次即可。
- 在Nest.js中装饰器可以解决依赖注入的问题,而依赖注入是Java等后端语言拥有的非常优秀的编程思想;有了依赖注入,可以大大降低项目的耦合度,大大提升项目的可拓展性。
:::
装饰器的分类
常见的装饰器:类装饰器、属性装饰器、方法装饰器、参数装饰器、元数据装饰器。2. 类装饰器
1. 类装饰器的实现
:::info 装饰器可分为:带参数的装饰器和不带参数的装饰器 ::: ```typescript // 不带参数的装饰器 function FirstClassDecorator(targetClass: any) { let targetClassObj = new targetClass(); targetClassObj.buy(); console.log(“targetClass.name: “, targetClass.name); }
// 带参数类装饰器案例 function FirstClassDecoratorParam(classinfo: string) { return function (targetClass: any) { console.log(targetClass.prototype.constructor.name + “ “ + classinfo); Object.keys(targetClass.prototype).forEach((methodname) => { console.log(“方法”, methodname); let dataprop = Object.getOwnPropertyDescriptor( targetClass.prototype, methodname ); console.log(“方法数据属性:”, dataprop); }); }; }
@FirstClassDecoratorParam(“param”) class CustomerService { name: string = “下单”; constructor() {} buy() { console.log(this.name + “购买”); } placeOrder() { console.log(this.name + “下单购买”); } }
<a name="u5oy3"></a>## 2. 类装饰器的JS源码```typescript"use strict";// 1. 底层JS 组合装饰器和目标类 __decorate函数var __decorate =(this && this.__decorate) ||function (decorators, target, key, desc) {// argsnum 参数个数var argsnum = arguments.length;// targetinfo 被装饰器修饰的目标(本案例为类)// argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]// argsnum=4 装饰器修饰的是方法(第四个参数desc等于null),// targetinfo=该方法的数据属性// desc = Object.getOwnPropertyDescriptor(target, key)// argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefinedvar targetinfo =argsnum < 3? target: desc === null? (desc = Object.getOwnPropertyDescriptor(target, key)): desc; //S100// decorator保存装饰器数组元素var decorator;// 元数据信息,支持reflect-metadata元数据if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {targetinfo = Reflect.decorate(decorators, target, key, desc);}// 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行elsefor (var i = decorators.length - 1; i >= 0; i--) {if ((decorator = decorators[i])) {// 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】// 执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类// 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo)// 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)// targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfotargetinfo =(argsnum < 3? decorator(targetinfo): argsnum > 3? decorator(target, key, targetinfo): decorator(target, key)) || targetinfo;console.log("targetinforesult:", targetinfo);}}return (argsnum > 3 &&targetinfo &&Object.defineProperty(target, key, targetinfo),targetinfo);};// 底层JS 组合装饰器和目标类 __decorate函数结束// 不带参数的装饰器function FirstClassDecorator(targetClass) {var targetClassObj = new targetClass();targetClassObj.buy();console.log("targetClass.name:", targetClass.name);}var CustomerService = /** @class */ (function () {function CustomerService() {this.name = "下单";}CustomerService.prototype.buy = function () {console.log(this.name + "购买");};CustomerService.prototype.placeOrder = function () {console.log(this.name + "下单购买");};CustomerService = __decorate([FirstClassDecorator], CustomerService);return CustomerService;})();// 带参数类装饰器案例function FirstClassDecoratorParam(classinfo) {return function (targetClass) {console.log(targetClass.prototype.constructor.name + " " + classinfo);Object.keys(targetClass.prototype).forEach((methodname) => {console.log("方法", methodname);let dataprop = Object.getOwnPropertyDescriptor(targetClass.prototype,methodname);console.log("方法数据属性:", dataprop);});};}let CustomerService = class CustomerService {constructor() {this.name = "下单";}buy() {console.log(this.name + "购买");}placeOrder() {console.log(this.name + "下单购买");}};CustomerService = __decorate([FirstClassDecoratorParam("param"), __metadata("design:paramtypes", [])],CustomerService);
3. 泛型工厂类继承装饰器
:::info 需求:对已经开发好的项目中的任意一个类,创建实例时,打印日志信息,输出哪一个类被创建了,并输出参数 :::
function LoggerInfoDecorator<T extends { new (...args: any): any }>(targetClass: T) {class LoggerSonClass extends targetClass {constructor(...args: any) {super(...args);console.log("日志信息...", targetClass.name);}methodone() {console.log("methodone:", this.name);}}return LoggerSonClass;}@LoggerInfoDecoratorclass Test {name!: string;age!: number;constructor(name: string) {this.name = name;}eat() {console.log(this.name, "吃饭");}}let test = new Test("wer"); //LoggerSonClass(test as any).methodone();// type TestConstructorType = new (...args: any) => Test;//let LoggerSonClass = LoggerInfoDecorator<TestConstructorType>(Test)let LoggerSonClass = LoggerInfoDecorator<typeof Test>(Test);let LoggerSonClassInstance = new LoggerSonClass("xiaoming");LoggerSonClassInstance.methodone();export {};
TS支持匿名类,所以装饰器也可以这么写
function LoggerInfoDecorator<T extends { new (...args: any): any }>(targetClass: T) {return class extends targetClass {constructor(...args: any) {super(...args);console.log("日志信息...", targetClass.name);}methodone() {console.log("methodone:", this.name);}};}
3. 方法装饰器
1. 方法装饰器的实现
/**** @param targetClassPrototype* @param methodName* @param methodDesc 数据属性*/function MyMethodDecorator(targetClassPrototype: any,methodName: string,methodDesc: PropertyDescriptor) {console.log("targetClassPrototype: ", targetClassPrototype);console.log("methodName: ", methodName);console.log("mehodDesc: ", methodDesc);methodDesc.value();}function MyMethodDecoratorWithParam(methodPath: string) {return function (targetClassPrototype: any,methodName: string,methodDesc: PropertyDescriptor) {console.log("methodPath: ", methodPath);console.log("targetClassPrototype: ", targetClassPrototype);console.log("methodName: ", methodName);console.log("mehodDesc: ", methodDesc);methodDesc.value(); // 可以直接执行传入的方法};}class RoleService {public roleName: string = "管理员";constructor() {}@MyMethodDecorator@MyMethodDecoratorWithParam("test")DistribRoles() {console.log("分配角色.....");}}export {};
2. 方法装饰器拦截器的意义
:::warning
在方法装饰器中拦截目标类的方法,可以壮大或者修改目标类的方法和功能。
比如:增加一个日志信息,采取该方法参数进行功能拓展处理。
:::
:::success
前置拦截:在方法执行前对方法进行处理
后置拦截:执行完方法后还有做别的事情
:::
实现一个简单的拦截器: 去除参数中的空格
// 方法装饰器class StringUtil {//工具类public static trimSpace(str: string): string {return str.replace(/\s+/g, "");}}function MethodInterceptor(methodPath: string) {return function (targetClassPrototype: any,methodName: string,methodDesc: PropertyDescriptor) {let targetMethodSave = methodDesc.value;//实现一个拦截器methodDesc.value = function (...args: any) {args = args.map((arg: any) => {if (typeof arg === "string") {return StringUtil.trimSpace(arg);}return arg;});targetMethodSave.apply(this, args);};};}class RoleService {public roleName: string = "管理员";constructor() {}@MethodInterceptor("test")DistribRoles(userName: string) {// 分配角色console.log("分配角色: ", userName);}}let roleService = new RoleService();roleService.DistribRoles("xiao ming");export {};
3. 底层JS源码
"use strict";// 1. 底层JS 组合装饰器和目标类 __decorate函数var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {// argsnum 参数个数var argsnum = arguments.length;// targetinfo 被装饰器修饰的目标// argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]// argsnum=4 装饰器修饰的是方法 [第四个参数desc等于null]// targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】// argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefinedvar targetinfo = argsnum < 3 ? target : desc === null ?desc = Object.getOwnPropertyDescriptor(target, key) : desc;// decorator保存装饰器数组元素var decorator;// 元数据信息,支持reflect-metadata元数据if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {targetinfo = Reflect.decorate(decorators, target, key, desc);} else// 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行for (var i = decorators.length - 1; i >= 0; i--) {if (decorator = decorators[i]) {// 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】// 执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo// 如果参数大于3【decorator为方法装饰器】// 直接执行 decorator(target, key, targetinfo)// 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】// 直接执行decorator(target, key)// targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回targetinfotargetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;console.log("targetinforesult:", targetinfo)}}return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;}Object.defineProperty(exports, "__esModule", { value: true });// 底层JS 组合装饰器和目标类 __decorate函数结束// 2. 工具类var StringUtil = /** @class */ (function () {function StringUtil () {}StringUtil.trimSpace = function (str) {return str.replace(/\s+/g, "");};return StringUtil;}());// 目标类var RoleService = /** @class */ (function () {function RoleService () {this.roleName = "管理员";}RoleService.prototype.DistribRoles = function (userName, isValid) {console.log("分配角色.....");};__decorate([MethodInterceptor("DistribRoles方法")],RoleService.prototype, "DistribRoles", null);return RoleService;}());// 3. 装饰器方法function MethodInterceptor (paramsValue) {console.log("方法装饰器....");return function (targetClassPrototype, methodName, methodDecri) {//targetMethodSave.value 表示原来目标类HttpClient的show()方法// 1.1 先保存目标类的方法到targetMethodSaveconsole.log("进入方法装饰器:methodDecri:", methodDecri);var targetMethodSave = methodDecri.value;console.log("targetMethodSave:", targetMethodSave);// 1.2.让value函数建立新得函数对象空间// value建立一个新的函数后,// RoleService对象调用DistribRoles;会执行value指向的新函数// 并不会执行原来RoleService目标类中DistribRoles方法// 这里建立的一个新函数就和后端 Java的spring AOP中的方法拦截器思想就完全一样methodDecri.value = function () {var args = [];for (var _i = 0; _i < arguments.length; _i++) {args[_i] = arguments[_i];}console.log("this:", this);// 迭代所有参数args = args.map(function (arg) {if (typeof arg === "string") {return StringUtil.trimSpace(arg);}return arg;});console.log(args);// 1.4.总结:这是一种典型的用方法装饰器扩大原来方法功能的案例// 1.5 但如果增强原来方法功能后,还想继续执行原来RoleService类中DistribRoles方法// 使用apply执行targetMethodSave原来函数targetMethodSave.apply(this, args);};// 方法执行之后,继续执行后续代码console.log("methodDecri.value:");};}
:::info
Object.defineProperty(target, key, targetinfo)在方法拦截器中的作用:
因为Object.getOwnPropertyDescriptor(Customer.prototype, "buy")每次获取的不是原来的prototype上的空间,而是开辟了一个新的空间。可以简单验证:重复使用该方法获取的出的对象空间不相等console.log(dataprops === dataprops2); //false。
所以,需要Object.defineProperty(target, key, targetinfo)将拦截器修饰过的方法重新赋值给原来的prototype。
:::
// 手动实现拦截// 该方法获取的不是实际的prototype中的对象空间,而是开辟出一个新的空间// 通过 dataprops === dataprops2 值为false即可验证。let dataprops = Object.getOwnPropertyDescriptor(Customer.prototype, "buy");let dataprops2 = Object.getOwnPropertyDescriptor(Customer.prototype, "buy");console.log(dataprops === dataprops2); //falselet savedMethod = dataprops.value;dataprops.value = function (...args) {args = args.map((arg) => {if (typeof arg === "string") {return StringUtil.trimSpace(arg);}return arg;});console.log("拦截方法的前置功能args: ", args);savedMethod.call(this, args);console.log("拦截方法的后置功能args: ", args);};// 如果不加这句话,就不能实现拦截器的功能Object.defineProperty(Customer.prototype, "buy", dataprops);let cust = new Customer();cust.buy("物 资");
4. 属性装饰器
1. 属性装饰器的实现
function loginProperty(attrValue: any) {return function (targetClassPrototype: any, attrName: string | symbol) {console.log("targetclassPrototype:", targetClassPrototype);console.log("attrname:", attrName);(targetClassPrototype.constructor as any).custLevelDescri = function () {console.log("消费5000元升级为贵宾");console.log("消费10000元升级为贵宾,赠送微波炉一个");};};}// 顾客目标类class CustomerService {public custname: string = "王五";@loginProperty("顾客登记")public degree!: string;constructor() {}show() {console.log("顾客名:", this.custname);}}(CustomerService as any).custLevelDescri();export {};
2. 底层JS源码
属性装饰器最终返回的结果是undefined
// 1. 底层JS 组合装饰器和目标类 __decorate 函数var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {// argsnum 参数个数var argsnum = arguments.length;// targetinfo 被装饰器修饰的目标【类或属性或方法或方法参数,本案例为类】// argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]// argsnum=4 装饰器修饰的是方法【第四个参数desc等于null]// targetinfo=该方法的数据属性 desc = Object.getOwnPropertyDescriptor(target, key)// argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo = undefinedvar targetinfo = argsnum < 3 ? target : desc === null ?desc = Object.getOwnPropertyDescriptor(target, key) : desc; //S100// decorator保存装饰器数组元素var decorator;// 元数据信息,支持reflect-metadata元数据if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {targetinfo = Reflect.decorate(decorators, target, key, desc);} else// 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行for (var i = decorators.length - 1; i >= 0; i--) {if (decorator = decorators[i]) {// 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)// 直接执行decorator装饰器,并传递目标targetinfo,这里是类// 如果参数大于3【decorator为方法装饰器】// 直接执行 decorator(target, key, targetinfo)// 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】// 直接执行decorator(target, key)// targetinfo最终为各个装饰器执行后的返回值,// 但如果没有返回值,直接返回第S100行的targetinfotargetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;console.log("targetinforesult:", targetinfo) // undefined}}// 属性装饰器最终返回的结果是undefinedreturn argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo),targetinfo;}// 底层 JS 组合装饰器和目标类 __decorate 函数结束// 2. 属性装饰器function loginProperty(attrValue) {return function (targetclassPrototype, attrname) {console.log("targetclassPrototype:", targetclassPrototype);console.log("attrname:", attrname);targetclassPrototype.constructor.custLevelDescri = function () {console.log("消费5000元升级为贵宾");console.log("消费10000元升级为贵宾,赠送微波炉一个");};};}// 3.目标类var CustomerService = /** @class */ (function () {function CustomerService() {this.custname = "王五";}CustomerService.prototype.show = function () {console.log("顾客名:", this.custname);};__decorate([loginProperty("顾客登记")], CustomerService.prototype, "degree");return CustomerService;}());CustomerService.custLevelDescri();
5. 其他装饰器
:::warning
装饰器执行顺序:
1. 属性装饰器 ==> 2. 方法参数装饰器 ==> 3. 方法装饰器 ===> 4. 构造器参数装饰器 ===> 4. 类装饰器
:::
1. 参数装饰器
function UrlParam(params: any) {return function paramDecorator(targetClassPrototype: any,methodname: string,paramindex: number) {console.log("targetClassPrototype:", targetClassPrototype);console.log("methodname:", methodname);console.log("paramindex:", paramindex);targetClassPrototype.info = params;};}class People {eat(@UrlParam("地址信息") address: string, who: string) {console.log("address:", address);}}// 执行结果targetClassPrototype: {}methodname: eatparamindex: 0
2. 构造函数参数装饰器
- 先安装 reflect-metadata 第三方包 【后面会有详细讲解,先简单了解即可】
- UserController 控制器类实现
- UserService 业务类实现
- 保存对象的集合类实现 【单件设计模式】
- 构造参数装饰器实现
3. 元数据操作
:::info 元数据
【定义】元数据指附加在对象、类、方法、属性、参数上的数据。
【作用】元数据用来帮助提供实现某种业务功能需要用到的数据。 :::1. 对象和对象属性上使用元数据
```typescript import ‘reflect-metadata’ // 1. 对象 let obj = { username: “罗斯福”, age: 23, info() { console.log(“信息”); } }
// 2.1 Reflect.defineMetadata 是一个重载的方法 // 定义格式 // 为类或者对象上定义元数据 Reflect.defineMetadata(metakey,metavalue,targetClassOrObject) // 为方法定义元数据 Reflect.defineMetadata(metakey,metavalue,targetprototype,methodname) // 为属性定义元数据 Reflect.defineMetadata(metakey,metavalue,targetprototype,propkey)
// 说明:打开 d.ts 定义描述文件说明:Reflect 是命名空间,defineMetadata 是命名空间中的一个方法。 // 2.2 在对象上定义元数据 Reflect.defineMetadata(‘firstdescribe’, ‘对象属性全部符合要求’, obj); Reflect.defineMetadata(‘seconddescribe’, ‘对象不可删除’, obj);
// 2.3 获取obj上metakey为 firstdescribe 的值 console.log(Reflect.getMetadata(‘firstdescribe’, obj))// 输出对象属性全部符合要求
// 2.4 获取obj上metakey不存在的值 console.log(Reflect.getMetadata(‘threedescribe’, obj))// 输出undefined
// 3 使用 Reflect.defineMetadata 在对象属性上定义元数据。 // 3.1 在对象属性上定义和获取元数据 Reflect.defineMetadata(‘usernamemetakey’, ‘用户名合法’, obj,”username”); Reflect.getMetadata(‘usernamemetakey’, obj, “username”));// 输出用户名合法
// 3.2 使用 Reflect.hasMetadata 查看对象或对象属性上是否存在某个元数据 if (Reflect.hasMetadata(‘describe’, obj)) { console.log(“obj存在describe元数据”); }
<a name="sEOhi"></a>### 2. 直接在类、方法上定义元数据```typescript// 1. 在类上定义元数据,只是格式不同,效果与上一节的defineMetadata一样@Reflect.metadata('decribe', '都是地球人')class People {@Reflect.metadata("descible", "姓名不能包含非法汉字")username = "wangwu"@Reflect.metadata("importinfo", "去吃陶然居好吗")eat() {}}// 2 获取元数据的方法和上一节一致// 2.1 获取类上的元数据console.log(Reflect.getMetadata('decribe', People));// 都是地球人// 2.2 获取方法上的元数据 第二个参数是原型console.log(Reflect.getMetadata('importinfo', People.prototype, 'eat'));//去吃陶然居好吗// 2.3 判断People.prototype 原型上 eat 方法上是否存在importinfo元数据if (Reflect.hasMetadata('importinfo', People.prototype, 'eat')) {console.log("hasMetadata=>People原型上存在eat方法的importinfo元数据");}// 3 定义子类class ChinesePeople extends People {guoYear() {}}// 4 子类获取父类原型上的方法 ———— hasMetadataif (Reflect.hasMetadata('importinfo', ChinesePeople.prototype, 'eat')) {console.log("hasMetadata=>ChinesePeople原型上通过继承也获取到了eat方法和eat方法的importinfo元数据");}// 5 获取自有元数据,但不能获取原型链上父类的元数据 ———— hasOwnMetadataif (Reflect.hasOwnMetadata('importinfo', ChinesePeople.prototype, 'eat')) {console.log("hasOwnMetadata=>ChinesePeople原型上存在eat方法的importinfo元数据");} else {console.log("hasOwnMetadata=>ChinesePeople原型上不存在eat方法的importinfo元数据");}
3. 直接在类属性上定义元数据
import 'reflect-metadata'// 为类定义元数据@Reflect.metadata("info", "地球人")class People {@Reflect.metadata('descible1', '居住地为主要城市')@Reflect.metadata('descible2', '上海')place: Array<string> = ["中国", "北京"]@Reflect.metadata('firstname', '第一个名字')@Reflect.metadata('lastname', '最后一个名字')getFullName(name: string, age: string): number {return 100}}// 获取元数据console.log(Reflect.getMetadata('info', People));//地球人console.log(Reflect.getMetadata("descible", People.prototype, 'place'));//roseconsole.log(Reflect.getMetadata('firstname', People.prototype, 'getFullName'))//Jimconsole.log(Reflect.getMetadata('lastname', People.prototype, 'getFullName'))//Jim// [// 'design:returntype',// 'design:paramtypes',// 'design:type',// 'lastname',// 'firstname'// ]// 获取People.prototype 上getFullName方法的全部元数据Key组成的数组console.log(Reflect.getMetadataKeys(People.prototype, "getFullName"));Reflect.getMetadataKeys(People.prototype).forEach((item) => {console.log("metadatakey:", item);})Reflect.getMetadataKeys(People.prototype, 'getFullName').forEach((metakey) => {console.log("11metadatakey:", metakey);console.log(Reflect.getMetadata(metakey, People.prototype, 'getFullName'));})// 获取People类上place方法的全部元数据Key组成的数组// 输出// [// 'design:type',// 'descible1',// 'descible2'// ]console.log(Reflect.getMetadataKeys(People.prototype, "place"));Reflect.getMetadataKeys(People.prototype, 'place').forEach((metakey) => {console.log("属性metadatakey:", metakey);console.log(Reflect.getMetadata(metakey, People.prototype, 'place'));})class ChinesePeople extends People {@Reflect.metadata("descible", "姓名不能包含非法汉字")guoYear(args: string) {}}console.log("getMetadataKeys==>查看父类上的方法...");console.log(Reflect.getMetadataKeys(ChinesePeople.prototype, 'getFullName'))//Jimconsole.log("getOwnMetadataKeys不能查看父类上的方法...");console.log(Reflect.getOwnMetadataKeys(ChinesePeople.prototype, 'getFullName'))//Jim
4. 3个重要且特殊的内置元数据key
:::success
- design:paramtypes
- 构造器所有参数数据类型组成的数组
- 类中方法全部参数的数据类型组成的数组
- design:type
- 获取类属性的数据类型
- 获取类方法参数的数据类型
- design:returntype
@Controller控制器装饰器,是用来支持页面的各种请求的类的装饰器@Service业务逻辑层类装饰器@Autowired自动装配,一般是把外部其他数据注入给当前属性或方法参数的装饰器dependencyid一个唯一标识符变量,作为@Autowired的实参,使用@Autowired为不同类属性或方法参数注入数据时,dependencyid用于区别这些不同的类singleton标注是否是单列注入的参数,可选target表示被注入的目标类 :::2. 依赖注入
:::success 【依赖注入的特点】创建和使用分离,外部给内部的属性赋值。
【好处】让项目在日后修改和变动时,不修改或者少量修改代码即可实现功能,大大提高了项目的可扩展性。 :::1. 初步实现
实现步骤:
- 建立伪接口类
UserServiceInter - 修改
UserService的名字为UserServiceImpl类 修改自动装配装饰器
@Autowired代码import UserServiceImpl from "./UserServiceImpl";// 伪接口export default class UserServiceInter {public static getServiceImplClass() {return UserServiceImpl;}}
export default class UserServiceImpl {constructor() {console.log("UserServiceImpl construct");}login(username: string, pwd: string, role: string) {console.log("进入service ...Login,username:", username);if (username === "admin" && pwd === "123" && role === "admin") {return true;} else {return false;}}register() {console.log("usersevice...register");}}
```typescript import “reflect-metadata”;
export default function Autowired(injectid: string): PropertyDecorator { return (targetClassPrototype, propertyKey) => { // PropClass=UserServiceInter伪接口类 let PropServiceClass = Reflect.getMetadata( “design:type”, targetClassPrototype, propertyKey ); let PropServiceImplClass = PropServiceClass.getServiceImplClass(); let PropServiceImplClassObj = new PropServiceImplClass(); Reflect.defineProperty(targetClassPrototype, propertyKey, { value: PropServiceImplClassObj, });
// 方法一: collection// collectionInstance.set(propertyKey, PropClassObj);// 方法二: Object.defineProperty// 好处:由于targetClassPrototype原型+propertyKey绝对是不会被覆盖的// 充分保证了数据属性中的value的对象的唯一性
}; }
```typescriptimport "reflect-metadata";import Autowired from "../decorator/autowiredDecortator";import UserServiceImpl from "../service/UserServiceImpl";import UserServiceInter from "../service/UserServiceInter";class UserController {@Autowired("userService") // 修改Inject为更专业的Autowired单词private userServiceImpl!: UserServiceInter;public login(): void {let userServiceImpl: UserServiceImpl = Reflect.getOwnPropertyDescriptor(UserController.prototype,"userServiceImpl").value;userServiceImpl.register();(this.userServiceImpl as UserServiceImpl).register();}}let controller = new UserController();controller.login();export {};
3. 复杂泛型-param参数的处理
:::info
express中可以自动实现param参数的识别,可以通过ts类型处理得到。
例如 type Param = RouteParameters<”/showFood/:foodname/:price”> = { foodname : string } & { price : string }
:::
RouteParameters的实现方式如下:
- 实现RemoveTail类型,可以移除字符串中后半部分,使用通配符可以实现移除指定类型的内容。
- 通过多次嵌套RemoveTail类型,得到GetRouteParameter类型。GetRouteParameter的作用就是获取第一个
/前,排除.和-影响的参数的类型。 - 基于GetRouteParameter,就可以实现最终的RouteParameters
``typescript type RemoveTail<S extends string, Tail extends string> = S extends${infer P}${Tail}` ? P : S;
let result: RemoveTail<”/showFood/:foodname/:price”, “/:foodname/:price”>; // result: “/showFood”
// /${string}>;
// result2: “:foodname.beijing-china”
type GetRouteParameter =
RemoveTail-${string}>,.${string}>;
let result3: GetRouteParameter<”:foodname.beijing-china/:price/:shopname”>; // result3: “:foodname”
```typescriptexport type RouteParameters<Route extends string> = string extends Route? ParamsDictionary: Route extends `${string}(${string}`? ParamsDictionary: Route extends `${string}:${infer Rest}`? (GetRouteParameter<Rest> extends never? ParamsDictionary: GetRouteParameter<Rest> extends `${infer ParamName}?`? { [P in ParamName]?: string }: { [P in GetRouteParameter<Rest>]: string }) &(Rest extends `${GetRouteParameter<Rest>}${infer Next}` ? RouteParameters<Next> : unknown): {};type resultType = RouteParameters<"/:foodname.beijing-china/:price/:shopname">;// { foodname: string } & { price: string } & { shopname: string };
