#HarmonyOS NEXT体验官# TypeScript装饰器 原创 精华
一、前言
你如果正在从事或者学习鸿蒙开发,那一定在用装饰器。你如果熟悉注解,那你第一眼看到装饰器,你肯定会想装饰器是不是就跟注解一样?装饰器到底是什么?它有什么作用?本文将带你探究装饰器的秘密。
二、定义
装饰器就是一个方法或者函数,可以注入到类、方法、属性。大家请记住,装饰器就是一个函数,装饰器就是一个函数,装饰器就是一个函数。装饰器在不修改原有类、方法、属性的基础上添加额外的功能。常见装饰器:类装饰器、方法装饰器、属性装饰器、参数装饰器、元数据装饰器。
三、类装饰器
类装饰器用在类上面。当在类上使用装饰器,就会把类交给装饰器,装饰器就可以使用类。
3、1不带参的类装饰器
function FirstClassDecorator(tagetClass: any) {
// 类名
console.log(tagetClass.name)
// 构造函数名称,跟类名一样
console.log(tagetClass.prototype.constructor.name)
Object.keys(tagetClass.prototype).forEach((methodName) => {
// 获取方法名称,方法是定义原型上的,可以通过原型获取方法
console.log(methodName)
// 获取数据属性,后面会介绍
let pro = Object.getOwnPropertyDescriptor(tagetClass.prototype, methodName)
console.log(pro)
})
}
// 在类上使用装饰器
@FirstClassDecorator
class Person {
name = "小明"
constructor() {}
buy(what: number) {
console.log(`${this.name}买东西`)
}
print() {
console.log(`${this.name}买东西`)
}
}
在上面的代码中,FirstClassDecorator是个函数,也就是装饰器,在Person类上使用@FirstClassDecorator来修饰。这样的话,Person类就交给了FirstClassDecorator装饰器,FirstClassDecorator函数中的targetClass就是Person,我们可以在FirstClassDecorator函数中创建Person对象,调用Person中的方法,可以获取类的名称,类的方法。
3、2带参的类装饰器
/**
* 装饰器是函数
*
* @param params 装饰器传递过来的参数
*/
function FirstClassDecorator(params: any) {
console.log(params)
// tagetClass被装饰器修饰的类,对于本例来说,targetClass就是Person类
return function(tagetClass: any) {
// tagetClass就是Person类,创建对象
let tagetClassObj = new tagetClass()
// 调用方法
tagetClassObj.buy()
// 获取Person类的名称
console.log(tagetClass.name)
console.log(tagetClass.prototype.constructor.name)
// 获取类中所有的方法
Object.keys(tagetClass.prototype).forEach((methodName) => {
console.log(methodName)
// 获取属性描述符
let pro = Object.getOwnPropertyDescriptor(tagetClass.prototype, methodName)
console.log(pro)
})
}
}
@FirstClassDecorator('我是带参数的类装饰器')
class Person {
name = "小明"
constructor() {}
buy() {
console.log(`${this.name}买东西`)
}
print() {
console.log(`${this.name}买东西`)
}
}
带参的类装饰器需要在FirstClassDecorator函数返回一个函数,在Person类中使用FirstClassDecorator需要传参。
以上就是类装饰器的使用,再次强调,装饰器就是个函数,我们可以自定义一个函数,在类上通过@函数名
的方式就实现了一个类装饰器。
3、3类装饰器原理
使用命令tsc命令可以将ts代码编译成js代码,下面的代码要求你有js基础,否则可能看不懂。
var __decorate = (this && this.__decorate) ||
function (decorators, target, key, desc) {
// 获取参数的个数,对于类装饰器来说,参数为2
var argsnum = arguments.length
// argsnum = 2,说明是类装饰器,targetInfo就是类
// argsnum = 4,说明是方法装饰器,targetInfo就是方法的数据属性
// argsnum = 3,说明是参数装饰器或者属性装饰器,targetInfo是undefined
var targetInfo = argsnum < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc
// 保存装饰器
var decorator;
// 元数据信息
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(targetInfo),传递targetInfo
// 参数个数大于3,说明是方法装饰器,执行装饰器函数decorator(target, key, targetInfo)
// 参数个数等于3,执行装饰器函数decorator(target, key)
targetInfo = (argsnum < 3 ? decorator(targetInfo) : argsnum > 3 ? decorator(target, key, targetInfo) : decorator(target, key)) || targetInfo;
}
}
return argsnum > 3 && targetInfo && Object.defineProperty(target, key, targetInfo), targetInfo;
};
/**
* 装饰器函数
*
* @param tagetClass 被装饰器修饰的类,对于本例来说,targetClass就是Person类
*/
function FirstClassDecorator(params) {
console.log(params);
return function (tagetClass) {
// tagetClass就是Person类,创建对象
var tagetClassObj = new tagetClass();
// 调用方法
tagetClassObj.buy();
// 获取Person类的名称
console.log(tagetClass.name);
console.log(tagetClass.prototype.constructor.name);
// 获取类中所有的方法
Object.keys(tagetClass.prototype).forEach(function (methodName) {
console.log(methodName);
// 获取方法属性
var pro = Object.getOwnPropertyDescriptor(tagetClass.prototype, methodName);
console.log(pro);
});
};
}
var Person = /** @class */ (function () {
function Person() {
this.name = "小明";
}
Person.prototype.buy = function () {
console.log("".concat(this.name, "买东西"));
};
Person.prototype.print = function () {
console.log("".concat(this.name, "买东西"));
};
Person = __decorate([
// __decorate传递两个参数,第一个参数数数组。为什么是数组?因为可以有多个装饰器
// 对于带参数的装饰器,先执行装饰器函数,装饰器函数返回一个函数
FirstClassDecorator('我是带参数的类装饰器')
], Person);
return Person;
}());
在上面的js代码中,注释写的很清楚了,大家对照注释更加容易理解。
四、方法装饰器
方法装饰器用在方法上面。
4、1方法装饰器用法
如下代码,方法装饰器的函数有三个参数,第一个参数是原型,方法时定义在原型上的,第二个参数是方法名,第三个参数是方法的数据属性。
/**
* 方法装饰器
*
* @param targetClassPrototype 原型
* @param methodName 方法名
* @param methodDeri 方法的数据属性
*/
function MyMethodDecorator(targetClassPrototype: any, methodName: string,
methodDeri: PropertyDescriptor) {
console.log(targetClassPrototype)
console.log(methodName)
console.log(methodDeri)
// 调用被装饰器修饰的方法
methodDeri.value()
}
function MyMethodDecoratorParams(params: any) {
console.log(params)
return function MyMethodDecorator(targetClassPrototype: any, methodName: string,
methodDeri: PropertyDescriptor) {
console.log(targetClassPrototype)
console.log(methodName)
console.log(methodDeri)
// 调用被装饰器修饰的方法
methodDeri.value()
}
}
class Role {
@MyMethodDecoratorParams('我是带参数的方法装饰器')
dis() {
console.log('角色')
}
}
4、2数据属性
上面提到了数据属性,如下代码,数据属性其实是个接口,我们注意下value字段,value代表被方法装饰器修饰的方法,我们可以调用value来执行被方法装饰器修饰的方法。
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?(): any;
set?(v: any): void;
}
4、3方法装饰器原理
使用命令tsc命令可以将ts代码编译成js代码
var __decorate = (this && this.__decorate) ||
function (decorators, target, key, desc) {
// 获取参数的个数,对于类装饰器来说,参数为2
var argsnum = arguments.length
// argsnum = 2,说明是类装饰器,targetInfo就是类
// argsnum = 4,说明是方法装饰器,targetInfo就是方法的数据属性
// argsnum = 3,说明是参数装饰器或者属性装饰器,targetInfo是undefined
var targetInfo = argsnum < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc
// 保存装饰器
var decorator;
// 元数据信息
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(targetInfo),传递targetInfo
// 参数个数大于3,说明是方法装饰器,执行装饰器函数decorator(target, key, targetInfo)
// 参数个数等于3,执行装饰器函数decorator(target, key)
targetInfo = (argsnum < 3 ? decorator(targetInfo) : argsnum > 3 ? decorator(target, key, targetInfo) : decorator(target, key)) || targetInfo;
}
}
return argsnum > 3 && targetInfo && Object.defineProperty(target, key, targetInfo), targetInfo;
};
/**
* 方法装饰器
*
* @param targetClassPrototype 原型
* @param methodName 方法名
* @param methodDeri 方法的数据属性
*/
function MyMethodDecorator(targetClassPrototype, methodName, methodDeri) {
console.log(targetClassPrototype);
console.log(methodName);
console.log(methodDeri);
methodDeri.value();
}
function MyMethodDecoratorParams(params) {
console.log(params);
return function MyMethodDecorator(targetClassPrototype, methodName, methodDeri) {
console.log(targetClassPrototype);
console.log(methodName);
console.log(methodDeri);
methodDeri.value();
};
}
var Role = /** @class */ (function () {
function Role() {
}
Role.prototype.dis = function () {
console.log('角色');
};
__decorate([
MyMethodDecoratorParams('我是带参数的方法装饰器')
], Role.prototype, "dis", null);
return Role;
}());
与类装饰器不同的是,类装饰器调用__decorate
函数传了两个参数,方法装饰器调用__decorate
函数传了四个参数。第一个参数仍然是装饰器函数,第二个参数是原型,因为方法是定义在原型上的,第三个参数是方法名,第四个参数为空。参数的个数为4,说明是方法装饰器,获取方法的数据属性,执行方法饰器函数,最后还需要调用Object.defineProperty(target, key, targetInfo)
。为了实现拦截器功能,我们会修改数据属性的value,value就指向了新的方法,如果不调用 Object.defineProperty(target, key, targetInfo)
,原方法不会被执行。每次获取方法的数据属性,都会开辟一个新的空间,改变数据属性的value是在新的空间改变,原来的value不受影响。
五、属性装饰器
属性装饰器用在属性上面。
5、1属性装饰器用法
如下代码,属性装饰器的函数有三个参数,第一个参数是原型,第二个参数是属性名。
/**
*
* @param params 属性装饰器传递过来的参数
* @returns
*/
function MyPropeetyDecorator(params: any) {
/**
* @param targetClassPrototype 原型
* @param attrName 属性名
*/
return function(targetClassPrototype: any, attrName: string) {
console.log(attrName)
}
}
class People {
@MyPropeetyDecorator('我是属性装饰器')
public name!:string
}
5、2属性装饰器原理
使用命令tsc命令可以将ts代码编译成js代码
var __decorate = (this && this.__decorate) ||
function (decorators, target, key, desc) {
// 获取参数的个数,对于类装饰器来说,参数为2
var argsnum = arguments.length
// argsnum = 2,说明是类装饰器,targetInfo就是类
// argsnum = 4,说明是方法装饰器,targetInfo就是方法的数据属性
// argsnum = 3,说明是参数装饰器或者属性装饰器,targetInfo是undefined
var targetInfo = argsnum < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc
// 保存装饰器
var decorator;
// 元数据信息
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(targetInfo),传递targetInfo
// 参数个数大于3,说明是方法装饰器,执行装饰器函数decorator(target, key, targetInfo)
// 参数个数等于3,执行装饰器函数decorator(target, key)
targetInfo = (argsnum < 3 ? decorator(targetInfo) : argsnum > 3 ? decorator(target, key, targetInfo) : decorator(target, key)) || targetInfo;
}
}
// 方法装饰器,调用Object.defineProperty(target, key, targetInfo)
return argsnum > 3 && targetInfo && Object.defineProperty(target, key, targetInfo), targetInfo;
};
function MyPropeetyDecorator(params) {
return function (targetClassPrototype, attrName) {
console.log(attrName);
};
}
var People = /** @class */ (function () {
function People() {
}
__decorate([
MyPropeetyDecorator('我是属性装饰器')
], People.prototype, "name");
return People;
}());
属性装饰器调用__decorate
函数传了三个参数,第一个参数仍然是装饰器函数,第二个参数是原型,第三个参数是属性名。参数的个数为3,说明是属性装饰器,直接执行属性装饰器函数。