- 使用修饰器(装饰器)学习
前言
装饰器是一种用来装饰或修改类、方法、属性等代码的特殊语法,它允许开发者在不修改原有代码结构的情况下,通过添加注解或元数据来扩展或修改其行为。
在 JavaScript 中,装饰器是 ECMAScript 2016 提案中引入的特性,它们允许我们像注释一样添加元数据或修改代码。装饰器是一种函数,可以附加到类、方法、访问器、属性或参数上。
装饰器通过 @ 符号和后面紧跟着装饰器函数的方式来使用。它可以应用于类、类的属性、方法等。在 JavaScript 中,常见的装饰器有类装饰器、方法装饰器、属性装饰器和参数装饰器。
- 类装饰器:应用于类构造函数,用来修改或扩展类的行为。
- 方法装饰器:应用于类的方法定义,可以修改或扩展方法的行为。
- 属性装饰器:应用于类的属性声明,用来修改属性的行为。
- 参数装饰器:应用于方法或访问器的参数,用来修改方法的行为。
环境配置
第一步: 新建一个文件夹运行pnpm init
生成package.json文件
第二步: 安装typescriptpnpm add typescript -S
第三步: 生成tsconfig.json文件pnpm tsc --init
第四步: 将tsconfig.json文件中的compilerOptions
选项中experimentalDecorators
和emitDecoratorMetadata
设置为true
1. 类装饰器
类装饰器在类声明之前被声明,可以用来监视、修改或替换类定义。 接收一个参数:
target
:类构造函数
当有两个构造器的时候从下往上执行
使用:
/**
* 类装饰器使用
* 当有两个装饰器的时候执行顺序是从下往上执行 先执行 console.log(target,2); 在执行 console.log(target,1);/
*/
function log(target:Function) {
console.log(target,1);// 打印出来是Test类的构造函数
// 在Test的原型链上添加 一个logtest方法
target.prototype.logtest = function () {
console.log("测试1");
}
}
function log2(target: Function) {
console.log(target,2);
// 在Test的原型链上添加 一个logtest方法
target.prototype.logtest2 = function () {
console.log("测试2");
}
}
@log
@log2
class Test {
}
const test :any= new Test()
test.logtest2() // 输出 测试2
test.logtest() // 输出 测试1
类装饰器工厂:
/**
* 可以接收多个参数 并且使用
*/
const Message = (flag?:boolean=false): ClassDecorator=> {
return (target: Function) => {
target.prototype.isLogin = flag
target.prototype.message = (content:string) => {
console.log(content);
}
}
}
@Message(true)
class LoginController {
login() {
console.log("登录业务处理");
if (this.isLogin) {
// 登录成功消息
this.message("登录成功消息");
}
}
}
new LoginController().login();
当
@Message(true)时就会在控制台打印两条信息
当@Message(false)
或@Message()时就会在控制台打印一条信息
2. 方法装饰器
方法装饰器是装饰器的一种类型,用于装饰类的方法。它允许你在方法定义之前修改、替换或扩展方法的行为。方法装饰器是一个函数,它接收三个参数:
target
:如果是静态方法第一个参数构造函数,如果是普通方法第一个参数是被装饰的方法所在的类的原型对象
name
:第二个参数是被装饰的方法的名称
descriptor
:第三个参数是方法的描述符
value
:被装饰方法的实际函数体(本身的函数)
writable
:如果为 false,该属性的值就不能被重新赋值。默认为 true
enumerable
:如果为 false,该属性就不能用 for...in 循环或 Object.keys() 枚举。默认为 true
configurable
:如果为 false,则不能删除该属性,也不能重新配置,而且不能改变该属性的可枚举性(enumerable),默认为 true
get
:获取函数(用于访问属性时调用)
set
:设置函数(用于设置属性值时调用)
通常,方法装饰器用来观察、修改或替换类中的方法,它可以拦截方法的调用并改变方法的行为。
普通方法使用装饰器:
const showDecorator: ClassDecorator = (target:Function,name?:string,descriptor?: PropertyDescriptor) => {
console.log(target,name,descriptor); // 输出1: 原型对象{} show {value:function,writable:true,enumerable: false,configurable: true}
target.name = "小明"
target.getName =function(){
console.log("原型上打印的name:",this.name); // 输出3: 原型上打印的name: 小明
}
}
class User {
@showDecorator
public show() {
console.log("show中打印的name:",this.name); // 输出2: show中打印的name: 小明
this.getName()
}
}
let user = new User()
user.show()
静态方法使用装饰器:
const showDecorator: ClassDecorator = (target:Function,name:string,descriptor: PropertyDescriptor) => {
console.log(target, name, descriptor); // 构造函数 show {value:function,writable:true,enumerable: false,configurable: true}
console.log("在构造器里调用静态的show"); // 输出1
target.show() // 输出2: 111
// descriptor.writable=false
}
class User {
@showDecorator
static show() {
console.log("1111");
}
}
// 如果上面 descriptor.writable=false 设置成了false 所以会报错 TypeError: Cannot assign to read only property 'show' of function 'class User
User.show = () => {
console.log("123"); // 输出3: 123
}
User.show()
方法装饰器工厂:
function delayDecorator(time: number) {
return (target: Function, name: string, descriptor: PropertyDescriptor) => {
const method:Function = descriptor.value;
descriptor.value = function () {
setTimeout(() => {
method.apply(this,arguments);
}, time);
};
return descriptor;
}
}
// 延时执行class
class Delay {
@delayDecorator(2000)
show(name:string) {
console.log("我是"+name);
}
}
const delay = new Delay();
delay.show("小王"); // 两秒之后才会打印我是小王
3. 属性装饰器
属性装饰器常常用于拦截对类属性的访问或者修改,以执行某些额外的逻辑。它可以用来修改属性的行为或者用于属性的元数据收集等场景。 接收参数:
target
:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象(和方法装饰器一样)
propertyKey
:要装饰的属性的名称
function logProperty(target:Function, propertyKey: string) {
console.log(target,propertyKey); // 输出原型对象 {} 参数名myProp
target.name = 'hello';
target[propertyKey]='world';
}
class MyClass {
@logProperty
myProp: string = '你好,世界';
}
const instance: any = new MyClass();
// 打印原型上的属性
console.log(instance.__proto__); // 输出{ name: 'hello', myProp: 'world' }
console.log(instance.myProp); // 输出你好世界
console.log(instance.name,instance.__proto__.myProp); //输出 hello world
4. 参数装饰器
通过参数装饰器,你可以在方法被调用时对方法的参数进行拦截和处理。你可以修改参数的类型、对参数值进行校验、记录参数的使用情况等。 接收参数:
target
:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象(和方法装饰器一样)
propertyKey
:使用方法的名称
index
:参数的索引
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
console.log(target,propertyKey,parameterIndex);
// 构造函数
if (typeof target === 'function' && target.prototype) {
target.__ooo__ = "小白";
} else { // 原型对象
target.name = "名称"
}
}
class MyClass {
myMethod( value: string@logParameter) {
console.log(this.name+":"+value);
}
static find(str: string@logParameter) {
console.log(this.cc,this.__ooo__,str);
}
}
const instance = new MyClass();
instance.myMethod('小明'); // 输出:名称:小明
MyClass.cc="小黑"
MyClass.find("你好世界1") // 输出 小黑 小白 你好世界1
console.log(instance.constructor); // [class MyClass] { __ooo__: '小白', cc: '小黑' }
5. 装饰器其他使用方法
使用装饰器动态转换属性:
function conversion(target:Function,key:string) {
let value:string=""
Object.defineProperty(target, key, {
get:()=> {
return value.toLocaleLowerCase()
},
set: (newValue:string) =>{
value=newValue
}
})
}
// 动态设置属性
class Person {
@conversion
title:string | undefined
}
const person: any = new Person()
person.title = 'XWYA'
console.log(person.title); // 输出小写xwya