Typescript的装饰器

Typescript
Author

曳影

Published

October 30, 2023

Typescript的装饰器

装饰器的定义

  • 装饰器
    • 就是一个函数
    • 可以注入到:方法属性参数对象
    • 扩展其功能
  • 装饰器要解决的问题
    • 装饰器就是解决在不修改类、属性、方法、参数的时候为其添加额外的功能。
  • 高阶组件本质上也采用了装饰器的思想
    • Nets.js装饰器可以解决依赖注入的问题
    • 有了依赖注入,能大大降低项目的耦合度,大大提升项目的扩展性
  • 依赖注入核心思想
    • 使用和创建分离

装饰器的分类

  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 参数装饰器
  • 元数据的装饰器

装饰器的两种写法

  • 使用时不传参
  • 装饰器工厂:使用时可以传参

环境搭建

  1. 安装ts-node - 可以使用ts-node命令直接运行*.ts文件,不需要先编译成*.js再使用node执行

    npm install -g ts-node
  2. 安装concurrently,支持合并执行,同时运行多个script命令

    npm i concurrently -s
    npm i nodemon -s
  3. 新建目录,初始化TS配置,生成tsconfig.json

    tsc --init
  4. 生成Node包管理文件pakcage.json shell npm init

  5. 配置文件修改配置支持,修改tsconfig.json - 解开装饰器配置

    • 支持普通装饰器
    • 支持元数据装饰器
      • 设置源码目录和编译生成JS代码目录
      "experimentalDecorators": true, // 支持普通装饰器
      "emitDecoratorMetadata": true,  // 支持元数据装饰器
      "rootDir": "./src",             // 源代码根目录
      "outDir": "./dist",             // 生成JS代码的根目录
  6. 配置package.json - 监控dist/teaching目录中的js文件,变化时执行node命令 - 合并启动 - 命令解决typescript编译装饰器类时出现的bug

    • tsconfig.json中配置装饰器配置这是避免代码报错
    • 编译执行需要在指定支持装饰器选项
      • ts-node命令配置
      • 这个配置需要根据文件名进行修改,后面会有提示
      "dev:build": "tsc -w",
      "dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/1ClassDecorator.js",
      "start": "concurrently npm:dev:*",
      "tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
      "ctrl": "ts-node src/controller/HomeController.ts",
      "beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
  7. 创建src/decorators

类装饰器

  • 不带参数的类装饰器
    • 代码

      function FirstClassDecorator(targetClass: any) {
      let targetClassObj = new targetClass()
      targetClassObj.buy()                                // 下单购买
      console.log("targetClass.name: ", targetClass.name) // targetClass.name:  CustomerService
      }
      
      @FirstClassDecorator
      class CustomerService {
        name: string = "下单"
      
        constructor() {}
      
        buy() {
          console.log(this.name + "购买")
        }
      
        placeOrder() {
          console.log(this.name + "下单购买")
        }
      }
      export {}
    • package.json配置

      • 当前代码的文件名1ClassDecorator.ts
      • 修改1ClassDecorator.js1ClassDecorator.js
      {
          "name": "ts-examples",
          "version": "1.0.0",
          "description": "",
          "scripts": {
            "dev:build": "tsc -w",
            "dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/1ClassDecorator.js",
            "start": "concurrently npm:dev:*",
            "tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
            "ctrl": "ts-node src/controller/HomeController.ts",
            "beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
          },
          "author": "",
          "license": "ISC",
          "dependencies": {
            "concurrently": "^8.2.2",
            "nodemon": "^3.0.1"
          }
        }
    • 运行代码

      npm run start
    • 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=undefined
        var targetinfo = argsnum < 3 ? target : 
        desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;
        console.log("target: ", target)         // target    :  [Function: CustomerService]
        console.log("targetinfo: ", targetinfo) // targetinfo:  [Function: CustomerService]
        // 由于无参数,argsnum < 3, 其实是torgetinfo = target
      
        // decorators: 保存``装饰器数组``元素
        // decorators = [FirstClassDecorator]
        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行的targetinfo
      
              // 这里返回一个新的targetinfo
              // 是把原来的targetinfo 传入到每一个装饰器函数中
              // 将CustomerService 传入到装饰器函数中,将执行逻辑交给装饰器内部执行
              // 如果函数有返回值
              // - 那么targetinfo就为装饰器的返回值
              // - 如果装饰器函数无返回值,则执行`|| targetinfo`
              targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
                decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
              console.log("targetinforesult:", targetinfo)
            }
          }
        // 参数数目小于三个argsnum < 3, 直接返回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); // targetClass.name:  CustomerService
      }
      
      // 被装饰的类
      var CustomerService = /** @class */ (function () {
          function CustomerService() {
              this.name = "下单";
          }
          CustomerService.prototype.buy = function () {
              console.log(this.name + "购买");
          };
          CustomerService.prototype.placeOrder = function () {
              console.log(this.name + "下单购买");
          };
          // 调用`__decorate`函数
          // - 参数
          //   - 装饰器列表
          //   - 类构造函数名称 CustomerService
          CustomerService = __decorate([
              FirstClassDecorator,
              __metadata("design:paramtypes", [])
          ], CustomerService);
          return CustomerService;
      }())
  • 带参数的类装饰器
    • 代码

      function FirstClassDecorator(params: any) {
        console.log("params out: ", params)
        return function(targetClass: any) {
          let targetClassObj = new targetClass()
          targetClassObj.buy()
          console.log("targetClass.name: ", targetClass.name)
          console.log("params in: ", params)
        }
      }
      
      @FirstClassDecorator("我是用来修饰CustomService类的装饰器参数")
      class CustomerService {
        name: string = "下单"
      
        constructor() {}
      
        buy() {
          console.log(this.name + "购买")
        }
      
        placeOrder() {
          console.log(this.name + "下单购买")
        }
      }
      
      export {}
    • package.json配置

      • 当前代码的文件名2ClassDecorator.ts
      • 修改1ClassDecorator.js2ClassDecorator.js
      {
          "name": "ts-examples",
          "version": "1.0.0",
          "description": "",
          "scripts": {
            "dev:build": "tsc -w",
            "dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/2ClassDecorator.js",
            "start": "concurrently npm:dev:*",
            "tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
            "ctrl": "ts-node src/controller/HomeController.ts",
            "beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
          },
          "author": "",
          "license": "ISC",
          "dependencies": {
            "concurrently": "^8.2.2",
            "nodemon": "^3.0.1"
          }
        }
    • 运行代码

      npm run start
    • 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=undefined
        var targetinfo = argsnum < 3 ? target : 
        desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;//S100
        console.log("target    : ", target)      // [Function: CustomerService]
        console.log("targetinfo: ", targetinfo)  // [Function: CustomerService] // argsnum < 3 targetinfo = target
      
        // decorators: 保存``装饰器的数组``元素
        // decorators = [FirstClassDecorator]
        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行的targetinfo
              targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
                decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
              console.log("Final targetinfo result:", targetinfo)
            }
          }
        return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
      }
      
      // 带参数的装饰器
      function FirstClassDecorator(params: any) {
        console.log("params out: ", params)
        return function(targetClass: any) {
          let targetClassObj = new targetClass()
          targetClassObj.buy()
          console.log("targetClass.name: ", targetClass.name)
          console.log("params in: ", params)
        }
      }
      
      // 被装饰的类
      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
      
          // 和不带参数的区别就是
          // - 会把带参数装饰器先执行一遍
          // - 带参数的装饰器,返回的是一个函数作为装饰器函数
          // - 然后传入被装饰的类
          CustomerService = __decorate([
            FirstClassDecorator("我是用来修饰CustomService类的装饰器参数"),
          ], CustomerService);
          return CustomerService;
      }());

泛型工厂继承装饰器

  • 毫无相干的两个类可以相互赋值
    • 条件是:属性相同
    • 必须拥有我的所有属性(可以多,不能少),才能赋值给我
    • 等号右边完全包含左边属性
    • 父类类名,可以接受子类的类名(多态)
  • 泛型工厂继承装饰器
    • 装饰器类作为子类,继承父类的属性和方法

    • 返回装饰器子类的类名

    • 完成类的功能扩展和替换

    • 代码

      // 1. 完成日志信息的装饰器
      function LoggerInfoDecorator<T extends { new(...args: any): any}>(targetClass: T) {
        class LoggerInfoDecorator extends targetClass {
          constructor(...args: any) {
            super(...args)
            console.log("Logging INFO targetClass: ", (targetClass as any).name)
          }
        }
        return LoggerInfoDecorator
      }
      
      // 2. 目标类
      @LoggerInfoDecorator
      class Test {
        name!: string
        age!: number
      
        // 1. 先执行原来的构造函数
        constructor(name: string) {
          this.name = name
        }
      
        eat() {
          console.log(this.name, "Eating Food")
        }
      }
      
      let test = new Test("Zhangsan")
      test.eat()
      
      export {}
    • JS源码

      //  1.  继承源码代码
      let __extends = (function (Son, Parent) {
      
        function getStaticExtendsWithForIn (Son, Parent) {
          for (let key in Parent) {
            if (Object.prototype.hasOwnProperty.call(Parent, key)) {
              Son[key] = Parent[key]
            }
          }
        }
      
        function getStaticExtendsWithObjectkeys (Son, Parent) {
          Object.keys(Parent).forEach((key) => {
            Son[key] = Parent[key]
          })
        }
      
        function getStaticExtendsWithProto (Son, Parent) {
          Son.__proto__ = Parent;
        }
      
        let MyextendStatics = function (Son, Parent) {
          let MyextendStatics = Object.setPrototypeOf || getStaticExtendsWithForIn ||
            getStaticExtendsWithObjectkeys || getStaticExtendsWithProto
          return MyextendStatics(Son, Parent)
        }
      
        return function (Son, Parent) {
          MyextendStatics(Son, Parent)
          function Middle () {
            this.constructor = Son;
          }
          if (Parent) {//如果不为空 如果父类存在
            Middle.prototype = Parent.prototype;
            Son.prototype = new Middle()
          } else {// 如果父类不存在
            Son.prototype = Object.create(null)
          }
          console.log("Object.create(null):", Object.create(null));
        }
      }())
      
      // 2. 底层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=undefined
        var 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行的targetinfo
              targetinfo = (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 函数结束
      
      // 2.装饰器类
      function LoggerInfoDecorator(targetClass) {
      var LoggerInfoDecorator = /** @class */ (function (_super) {
          __extends(LoggerInfoDecorator, _super);  // ****继承关系****
          function LoggerInfoDecorator() {
              var args = [];
              for (var _i = 0; _i < arguments.length; _i++) {
                  args[_i] = arguments[_i];
              }
              var _this = _super.apply(this, args) || this;
              console.log("Logging INFO targetClass: ", targetClass.name);
              return _this;
          }
          return LoggerInfoDecorator;
      }(targetClass));
          return LoggerInfoDecorator;
      }
      // 2. 目标类
      var Test = /** @class */ (function () {
          // 1. 先执行原来的构造函数
          function Test(name) {
              this.name = name;
          }
          Test.prototype.eat = function () {
              console.log(this.name, "Eating Food");
          };
          Test = __decorate([
              LoggerInfoDecorator,
          ], Test);
          return Test;
      }());
      
      // 4.测试
      var test = new Test("ok");

方法装饰器

  • 包含以下内容

    • 方法装饰器的实现
    • 方法装饰器拦截器意义
    • 拦截器的前置、后置功能实现
  • 不带参数方法装饰器实现

    • 代码

      /* 
      @param targetClassPrototype 类名
      @param methodname : value  方法名
      @param methodDecri: PropertyDescriptor 数据属性
      
      PropertyDescriptor的定义
      - 用来控制当前方法的属性
      
      interface PropertyDescriptor {
        configurable?: boolean   // 是否可配置
        enumerable?: boolean     // 是否可枚举
        value?: any              // 方法名
        writable?: boolean       // 是否可写
        get?(): any              // get属性
        set?(v: any): void       // set属性
      }
      */
      
      function MyMethodDecorator(targetClassPrototype: any, methodname: string, methodDecri: PropertyDescriptor) {
        console.log("targetClassPropertotype: ", targetClassPrototype)
        console.log("key: ", methodname)
        console.log("Data Attr: ", methodDecri)
        methodDecri.value() // 当前被装饰的函数,数据属性中的value属性,这里就是执行被修饰的函数
      }
      
      // 目标类
      class RoleService {
        roleName: string = "Admin"
        constructor () {}
      
        @MyMethodDecorator
        DistribRoles() {
          console.log("Generating Role ...")
        }
      }
  • 带参数方法装饰器实现

    • 代码

      /* 
      @param targetClassPrototype 类名
      @param methodname : value  方法名
      @param methodDecri: PropertyDescriptor 数据属性, value 表示的是方法体
      
      PropertyDescriptor的定义
      - 用来控制当前方法的属性
      
      interface PropertyDescriptor {
        configurable?: boolean   // 是否可配置
        enumerable?: boolean     // 是否可枚举
        value?: any              // 方法名
        writable?: boolean       // 是否可写
        get?(): any              // get属性
        set?(v: any): void       // set属性
      }
      */
      
      function MyMethodDecorator(params: any) {
        return function (targetClassPrototype: any, methodname: string, methodDecri: PropertyDescriptor) {
          console.log("targetClassPropertotype: ", targetClassPrototype)
          console.log("key: ", methodname)
          console.log("Data Attr: ", methodDecri)
          console.log("Params: ", params)
          methodDecri.value() // 当前被装饰的函数,数据属性中的value属性,这里就是执行被修饰的函数
        }
      }
      
      // 目标类
      class RoleService {
        roleName: string = "Admin"
        constructor () {}
      
        @MyMethodDecorator("/Service")
        DistribRoles() {
          console.log("Generating Role ...")
        }
      }
  • 方法拦截器的意义

    • 改良原有的函数,可以添加前置和后置的处理代码

属性装饰器

  • 装饰器参数
    • targetClassPrototype: object
      • 类名
    • attrname: string | symbol
      • 属性名
  • 属性装饰器的应用场景
    • 在属性声明时
      • 进行拦截
      • 添加逻辑
      • 存储相关的元数据
  • 属性装饰器应用: 元数据注入
    • 可以使用属性装饰器为属性附加额外的元数据
      • 在某些框架中非常有用,比如Angular的@Input()

      • 用于定义一个类的属性为输入属性

        class MyComponent {
          @Input title: string
        }
  • 属性装饰器应用:验证
    • 通过属性装饰器来验证类的属性值
      • 可以定义一个@IsPositive装饰器,确保属性值是正数

        function IsPositive(target: any, key: string) {
          const storedValue = this[key]
          Object.defineProperty(target, key, {
            get: function() {
              return storedValue
            },
            set: function(value) {
              if (value <= 0) {
                throw new Error("Value should be a positive number")
              }
              storedValue = value
            }
          })
        }
        
        class MyClass {
          @IsPositive
          positiveNumber: number
        }
    • 属性装饰器应用:日志与审计
      • 每次属性值更改时记录日志

        function LogChanges(target: any, key: string) {
          let storedValue = this[key]
          Object.defineProperty(target, key, {
            get: function() {
              return storedValue
            },
            set function(value) {
              console.log(`Property ${key} changed from ${storedValue}` to ${value})
            }
          })
        }
        
        class MyClass {
          @LogChanges
          myProperty: string
        }
    • 属性装饰器的其他应用场景
      • 延迟加载/懒加载
        • 当访问某个属性时,如果数据还没有被加载,则从后端或其他数据源动态加载
      • 自动绑定
        • 在React中自动绑定方法到当前实例,以便在回调或事件处理程序中使用正确的this
      • 存储映射关系
        • 在ORM框架中,属性装饰器可以用来定义属性与数据库列之间的映射关系
      • 计算属性的缓存
        • 对于计算成本高的属性,可以使用装饰器来缓存它的值,只在需要时计算

参数装饰器

  • 含义
    • 应用于类构造函数或方法参数
    • 它们在运行时可以接触到所属的类、方法和参数索引等信息
  • 参数装饰器的应用场景
    • 依赖注入
      • 当类的实例被创建时,所需的依赖会自动被注入到构造函数参数中
    • 元数据标记
      • 在某些场景下,你可能想为某个参数标记元数据,之后这些元数据可以用于验证、转换或其他目的
    • 日志和审计
      • 可以用来记录或审计方法调用和参数值,特别是对于某些关键参数
    • 转换或序列化
      • 在方法调用之前,参数装饰器可以用来转换或序列化参数值
    • 参数校验
      • 参数装饰器可以与反射元数据结合使用,对方法参数进行类型校验
    • 访问控制和授权
      • 在某些场景下,可以使用参数装饰器检查用户权限,决定是否允许对某个方法进行访问或操作
    • 关联路由和请求数据
      • 在NestJS,参数装饰器可以用来提取路由参数、查询参数、请求体或请求头

访问器装饰器

  • 定义
    • 用于拦截类的访问器(getset)
  • 访问器装饰器的应用场景
    • 值的验证
      • 在为属性赋值时(通过setter)对其进行验证

        function Validate(target: any, key: string, descriptor: PropertyDescriptor) {
          const originSet = descriptor.set
          descriptor.set = function (value: any) {
            if (!value) {
              throw new Error('Invalid value')
            }
            originSet?.call(this, value)
          }
        }
        
        class MyClass {
          private _name: string
        
          @Validate
          set name(value: string) {
            this._name = value
          }
        }
      • 日志

        • 获取或设置属性值时进行日志记录

          function Log(target: any, key: string, descriptor: PropertyDescriptor) {
            const originalGet = descriptor.get
            const originalSet = descriptor.set
          
            if (originalGet) {
              descriptor.get = function () {
                console.log(`Getting ${key}`)
                return originalGet.call(this)
              }
            }
          
            if (originalSet) {
              descriptor.set = function (value: any) {
                console.log(`Setting ${key} to ${value}`)
                originalSet.call(this, value)
              }
            }
          }
          
          class MyClass {
            private _value: number
          
            @Log
            get value(): number {
              return this._value
            }
          
            set value(v: number) {
              this._value = v
            }
          }
      • 延迟初始化/懒加载

        • 在第一次访问属性时初始化它
      • 计算属性的缓存

        • 对于计算代价高昂的属性,可以使用访问器装饰器来缓存其值
      • 自动通知

        • 当属性值改变时,自动触发某些动作或事件
      • 数据绑定和观察者模式

        • 当属性值更改时,通知关联的UI或其他依赖项

元数据装饰器

  • 元数据
    • 为了帮助类、方法、属性实现一定的功能,而附加在其上的一些数据
    • 分类
      • 自定义元数据
      • 内置元数据(reflect-metadata自带)
  • 初步理解
    • 在定义类或类方法或对象的时候,可以设置一些元数据,我们可以获取到在类与类方法上添加元数据
    • 需要引入第三方库reflect-metadata
    • 采用@Reflect.metadata来实现
    • 元数据是指描述东西时用的数据

装饰器执行顺序

  • 执行顺序
    • 属性装饰器
    • 方法参数装饰器
    • 方法装饰器
    • 类装饰器
  • 装饰器的使用方式
    • 前面顺序的装饰器可以保存数据
    • 后面顺序的装饰器可以获取数据

装饰器总结

  • 属性装饰器(Property Decorators)
    • 应用于属性声明
      • 不可以修改属性的描述
      • 但可以使用它来存储关于属性的元数据
    • 属性装饰器的表达式将在运行时作为函数调用,并带有以下两个参数
      • 静态成员的类构造函数,或实例成员的类原型
      • 成员名称
  • 参数装饰器(Parameter Decorators)
    • 应用于类构造函数方法的参数
    • 参数装饰器在运行时可以获取关于参数的信息
    • 参数装饰器的表达式将在运行时作为函数调用,并带有以下三个参数
      • 静态成员的类构造函数,或实例成员的类原型(the prototype of the class for an instance member)
      • 成员名称(the name of member)
      • 参数函数参数列表(function parameter list)中的序号索引(index of the parameter)
  • 方法装饰器(Method Decorators)
    • 应用于方法的属性描述符
    • 可用于观察、修改或替换方法定义
  • 类装饰器(Class Decorators)
    • 应用于类的构造函数
    • 可以用来监视、修改或替换类的定义
  • 访问器装饰器(Parameter Decorators)
    • 应用于对象的访问器属性描述符
    • 不能同时使用在一个成员的getset访问器,只能应用于其中之一
  • 案例代码
    • 代码

      function ClassDecorator() {
        console.log('ClassDecorator: evaluated');
        return function (target) {
            console.log('ClassDecorator: called');
        }
      }
      
      function PropertyDecorator() {
          console.log('PropertyDecorator: evaluated');
          return function (target, propertyKey) {
              console.log('PropertyDecorator: called');
          }
      }
      
      function MethodDecorator() {
          console.log('MethodDecorator: evaluated');
          return function (target, propertyKey, descriptor) {
              console.log('MethodDecorator: called');
          }
      }
      
      function ParameterDecorator() {
          console.log('ParameterDecorator: evaluated');
          return function (target, propertyKey, parameterIndex) {
              console.log('ParameterDecorator: called');
          }
      }
      
      @ClassDecorator()
      class MyClass {
      
          @PropertyDecorator()
          myProperty: string;
      
          myMethod(@ParameterDecorator() param: string) {
              @MethodDecorator()
          }
      }
    • 执行结果

      PropertyDecorator: evaluated
      ParameterDecorator: evaluated
      MethodDecorator: evaluated
      ClassDecorator: evaluated
      ParameterDecorator: called
      MethodDecorator: called
      PropertyDecorator: called
      ClassDecorator: called