day4-TS学习之旅-装饰器

简介

  1. 装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁
  2. 装饰器自2005年在ECMAScript-6(ES6)中杯提出到限制,已将近10年
  3. 截止目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持
  4. 装饰器有五种:类装饰器、属性装饰器、方法装饰器、访问器装饰器、参数装饰器

类装饰器

  1. 基本语法
    类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑
    function Demo(target: Function) {
      console.log(target);
    }
    
    @Demo
    class Person {
      name: string;
      age: number;
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    }
    
  2. 应用举例
    需求:定义一个装饰器,实现Person实例调用toString时返回JSON.stringify的执行结果
    function CustomString(target: Function) {
      target.prototype.toString = function () {
        return JSON.stringify(this);
      };
      // 密封对象,禁止添加、删除或重新配置属性
      // Object.seal(target.prototype);
    }
    
    @CustomString
    class Person {
      name: string;
      age: number;
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
     
    }
    
    const person = new Person("张三", 18);
    console.log(person);
    console.log(person.toString());
    console.log(JSON.stringify(person));
    
    
    // interface Person {
    //   x: number;
    // }
    // Person.prototype.x = 1;
    // console.log(Person.prototype.x);
    
  3. 关于返回值
    类装饰器返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类
    类装饰器返回值:若类装饰器无返回值或返回undefined,那被装饰的类不会被替换
    function Demo(target: Function) {
      return class{
        test(){
          console.log(200);
          
        }
      }
    }
    @Demo
    class Person {
      test(){
        console.log(100);
      }
    }
    
    console.log(Person);
    
  4. 关于构造类型
    在TS中,Function类型所表示的范围十分广泛,包括:普通函数,箭头函数,方法等等,但并非Function类型的函数都可以被new关键字实例化,例如箭头函数是不能被实例化的,那么TS中如何声明一个构造类型呢?
    1. 仅声明构造类型
      /**
       * 构造函数类型
       * new 表示:该类型是可以用new操作符调用的
       * ...args 表示:该类型可以接受任意数量的参数
       * any[] 表示:该类型可以接受任意类型的参数
       * {} 表示:返回类型是对象(非 null,非 undefined )
       */
      type Constructor = new (...args: any[]) => {};
      
      function test(target: Constructor) {}
      
      class Person {}
      
      test(Person);
      
    2. 声明构造类型+指定静态属性
      type Constructor = {
        new (...args: any[]): {};
        wife:string //静态属性
      };
      
      function test(target: Constructor) {}
      
      class Person {
        static wife: "张三";
      }
      
      test(Person);
      
      
  5. 替换被装饰的类
    对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例如添加新的方法和状态。
    需求:设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取创建时间。
    type Constructor = new (...args: any[]) => {}
    interface Person {
      getCreatedTime(): void;
    }
    
    function LogTime<T extends Constructor>(target: T) {
      return class extends target {
        createdTime: Date;
        constructor(...args: any[]) {
          super(...args);
          this.createdTime = new Date();
        }
        getCreatedTime() {
          return `this.createdTime: ${this.createdTime}`;
        }
      }
    }
    @LogTime
    class Person {
      constructor(public name: string, public age: number) { }
      speak() {
        console.log("说话");
      }
    }
    
    
    const person = new Person("张三", 18);
    console.log(person);
    console.log(person.getCreatedTime());
    

装饰器工厂

装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活的控制装饰器的行为
需求:定义一个LogInfo类装饰器工厂,实现Person实例可以调用到introduce方法,且introduce中输出内容的次数,由LogInfo接收的参数决定。

interface Person {
  name: string;
  age: number;
  introduce: () => void;
}
function LogInfo(n:number) {
  return function (target: Function) {
    target.prototype.introduce = function () {
      for (let i = 0; i < n; i++) {
        console.log("我的名字是" + this.name + ",我今年" + this.age + "岁");
      }
    }
  }
}
@LogInfo(2)
class Person {
  constructor(public name: string, public age: number) { }
  
  speak() {
    console.log("说话");
  }
}
const person = new Person("张三", 20);
person.introduce();

装饰器组合

装饰器可以组合使用,执行顺序为:先【由上到下】的执行所有的装饰器工厂,依次获取到装饰器,然后再【由下到上】执行所有的装饰器。

  1. 执行顺序
    // 装饰器
    function test1(target: Function) {
        console.log('test1')
    }
    
    // 装饰器工厂
    function test2() {
        console.log('test2工厂')
        return function(target: Function) {
            console.log('test2')
        }
    }
    // 装饰器工厂
    function test3() {
        console.log('test3工厂')
        return function(target: Function) {
            console.log('test3')
        }
    }
    // 装饰器
    function test4(target: Function) {
        console.log('test4')
    }
    
    @test1
    @test2()
    @test3()
    @test4
    class Person {}
    
    /**
     * test2工厂
     * test3工厂
     * test4
     * test3
     * test2
     * test1
     */
    
  2. 组合应用
    
    interface Person {
        introduce: () => void
        getCreatedTime: () => void
    }
    
    // 使用装饰器重写toString方法+封闭其原型对象
    function CustomToString(target: Function) {
        target.prototype.toString = function() {
            return JSON.stringify(this)
        }
        Object.seal(target.prototype)
    
    }
    
    
    
    // 定义一个装饰器工厂LogInfo,它接受一个参数n,并返回一个类装饰器
    function LogInfo(n: number) {
        return function(target: Function) {
            target.prototype.introduce = function() {
                for (let i = 0; i < n; i++) {
                    console.log(`我叫${this.name},我今年${this.age}`)
                }
            }
        }
    }
    // 创建一个装饰器,为类添加日志功能和创建时间
    type Constructor = new (...args: any[]) => {}
    function LogTime<T extends Constructor>(target: T) {
        return class extends target {
            createdTime: Date;
            constructor(...args: any[]) {
                super(...args)
                this.createdTime = new Date()
            }
           getCreatedTime() {
            return `创建时间: ${this.createdTime}`
           }
        }
    }
    
    @CustomToString
    @LogInfo(3)
    @LogTime
    class Person {
        constructor(public name: string, public age: number) {}
        speak(){
            console.log(`你好啊`)
        }
    }
    
    const p = new Person('张三', 18)
    p.speak()
    console.log(p.toString())
    p.introduce()
    console.log(p.getCreatedTime())
    
    

属性装饰器

注意报错:作为表达式调用时,无法解析类修饰器的签名

//tsconfig.json文件
{
  "compilerOptions": {
   //...
    "experimentalDecorators": true
  }
}
  1. 基本语法

    /*
     * 参数说明 
     * target:对于静态属性来说值是类,对于实例属性来说值是类的原型对象
     * propertyKey: 被修饰的属性名
     */
    
    function Demo(target: object, propertyKey: string) {
        console.log(target, propertyKey)
    }
    
    class Person {
        @Demo  name: string
        @Demo  age: number
        @Demo  static school: string
        constructor(name: string, age: number) {
            this.name = name
            this.age = age
        }
    }
    
    
  2. 属性遮蔽
    如下代码中,当构造器中的this.age=age试图在实例上赋值时,实际上是调用了原型上age属性的set方法

    class Person {
          name: string
          age: number
          static school: string
        constructor(name: string, age: number) {
            this.name = name
            this.age = age
        }
    }
    let value = 10
    Object.defineProperty(Person.prototype, 'age', {
        get() {
            return value
        },
        set(val) {
            value = val
        }
    })
    const p = new Person('张三', 18)
    console.log(p)
    
  3. 应用举例
    需求:定义一个State属性装饰器,来监视属性的修改。

    function state(target: object, propertyKey: string) {
       let key =`_${propertyKey}`
       Object.defineProperty(target, propertyKey, {
        get() {
            return this[key]
        },
        set(val) {
            console.log(`${propertyKey}被修改为:${val}`)
            this[key] = val
        },
        enumerable: true,
        configurable: true
       })
    }
    class Person {
        name: string
        @state age: number
        constructor(name: string, age: number) {
            this.name = name
            this.age = age
        }
        
    }
    
    const p = new Person('张三', 18)
    
    

方法装饰器

  1. 基本语法
    /**
     * 参数说明:
     * target:对于静态方法来说值是类,对于实例方法来说值是类的原型对象
     * propertyKey: 方法的名称
     * descriptor: 方法的描述对象,其中value属性是被装饰的方法
     * writable属性表示是否可写,enumerable属性表示是否可枚举,configurable属性表示是否可配置
     
     */
    
    function Demo(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(target, propertyKey, descriptor)
    }
    
    class Person {
    
        constructor(public name: string, public age: number) {
    
        }
        @Demo
        speak() {
            console.log(`你好啊`)
        }
        static isAdult(age: number) {
            return age >= 18
        }
    }
    
    const p = new Person('张三', 18)
    p.speak()
    console.log(Person.isAdult(18))
    
  2. 应用实例
    需求:
    1. 定义一个Logger方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑。
    2. 定义一个Validate方法装饰器,用于验证数据。
      function Logger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
          const originalMethod = descriptor.value
          descriptor.value = function (...args: any[]) {
              console.log(`日志记录:${propertyKey}被调用,参数为:${JSON.stringify(args)}`)
              const result = originalMethod.call(this, ...args)
              console.log(`日志记录:${propertyKey}返回值为:${JSON.stringify(result)}`)
              return result
          }
      }
      
      function Validate(maxValue: number) {
          return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
              const originalMethod = descriptor.value
              descriptor.value = function (...args: any[]) {
                  if (args[0] > maxValue) {
                      throw new Error(`年龄非法`)
                  }
                  return originalMethod.apply(this, args)
              }
          }
      }
      class Person {
      
          constructor(public name: string, public age: number) {
      
          }
          @Logger
          speak() {
              console.log(`你好啊,${this.name},${this.age}`)
          }
          @Validate(14)
          static isAdult(age: number) {
              return age >= 18
          }
      }
      
      const p = new Person('张三', 18)
      p.speak()
      console.log(Person.isAdult(18))
      

访问器装饰器

  1. 基本语法
    /**
     * 参数说明
     * target: 对于实例访问器来说值是【所属类的原型对象】,对于静态访问器来说值是【所属类】
     * propertyKey: 访问器的名称
     * descriptor: 描述对象
     */
    function Demo(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(target)
        console.log(propertyKey)
        console.log(descriptor)
    }
    class Person {
        @Demo
        get address() {
            return '北京'
        }
        @Demo
        static get country() {
            return '张三'
        }
    }  
    
  2. 应用举例
    需求:对于Weather类的temp属性的set访问器进行限制,设置的最低温度 -50,最高温度 50
    class Weather {
        private _temperature: number;
        constructor(temperature: number) {
            this._temperature = temperature;
        }
        @RangeValidator(-50, 50)
        set temperature(value: number) {
            this._temperature = value;
        }
        get temperature() {
            return this._temperature;
        }
    }
    
    function RangeValidator(min: number, max: number) {
        return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            // 保存原方法
            const originalMethod = descriptor.set;
            // 重写set方法
            descriptor.set = function (value: number) {
                if (value < min || value > max) {
                    throw new Error(`温度非法`)
                }
                originalMethod?.call(this, value)
            }
        }
    }
    
    const weather = new Weather(20)
    weather.temperature = 90
    console.log(weather.temperature)
    
    

参数装饰器

  1. 基本语法

    /**
     * 参数说明
     * target: 如果修饰的是实例方法的参数,则target是类的原型对象,如果修饰的是静态方法的参数,则target是类
     * propertyKey: 访问器的名称
     * parameterIndex: 参数在函数参数列表中的索引,从0开始
     */
    
    
    function Demo(target: any, propertyKey: string, parameterIndex: number) {
        console.log(target)
        console.log(propertyKey)
        console.log(parameterIndex)
    }
    
    class Person {
        constructor(public name: string, public age: number) {
        }
        speak(@Demo message: string, msg: string) {
            console.log(`${this.name}说:你好,${message},${msg}`)
        }
    }
    
    const person = new Person('张三', 18)
    person.speak('你好', '世界')
    

你可能感兴趣的:(学习,开发语言,typescript)