js使用修饰器使用(记录学习)

31 阅读5分钟
  • 使用修饰器(装饰器)学习

前言

装饰器是一种用来装饰或修改类、方法、属性等代码的特殊语法,它允许开发者在不修改原有代码结构的情况下,通过添加注解或元数据来扩展或修改其行为。

在 JavaScript 中,装饰器是 ECMAScript 2016 提案中引入的特性,它们允许我们像注释一样添加元数据或修改代码。装饰器是一种函数,可以附加到类、方法、访问器、属性或参数上。

装饰器通过 @ 符号和后面紧跟着装饰器函数的方式来使用。它可以应用于类、类的属性、方法等。在 JavaScript 中,常见的装饰器有类装饰器、方法装饰器、属性装饰器和参数装饰器。

  • 类装饰器:应用于类构造函数,用来修改或扩展类的行为。
  • 方法装饰器:应用于类的方法定义,可以修改或扩展方法的行为。
  • 属性装饰器:应用于类的属性声明,用来修改属性的行为。
  • 参数装饰器:应用于方法或访问器的参数,用来修改方法的行为。

环境配置

第一步: 新建一个文件夹运行pnpm init 生成package.json文件

第二步: 安装typescriptpnpm add typescript -S

第三步: 生成tsconfig.json文件pnpm tsc --init

第四步: 将tsconfig.json文件中的compilerOptions选项中experimentalDecoratorsemitDecoratorMetadata设置为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