@decorator修饰器

182 阅读4分钟

一、核心概念与本质

1. 定义
装饰器(Decorator)是一种函数语法糖,用于在不修改原函数/类的前提下,为其添加额外功能。本质是一个高阶函数,接收目标对象作为参数,返回增强后的对象。

2. 应用场景

  • 日志记录、权限校验、性能监控等横切关注点
  • 框架层面的功能扩展(如Vue3组合式API、NestJS路由)
  • 类属性的元数据管理(如TS的反射机制)

二、装饰器的类型与语法

1. 四种装饰器类型

类型作用对象参数说明
类装饰器class接收类构造函数作为参数
方法装饰器类的方法接收类原型、方法名、描述符对象
属性装饰器类的属性接收类原型、属性名
参数装饰器方法的参数接收类原型、方法名、参数索引

2. 基本语法(以类装饰器为例)

// 装饰器工厂(可传参的装饰器)
function Logger(target: Function) {
  console.log(`[装饰器] 类 ${target.name} 被装饰`);
  // 扩展目标类的行为
  target.prototype.log = function() {
    console.log(`实例 ${this.name} 日志`);
  };
}

@Logger // 应用装饰器
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const user = new User('张三');
(user as any).log(); // 输出:实例 张三 日志

三、装饰器的执行时机与参数详解

1. 执行顺序

  • 类装饰器:在类定义时立即执行(编译阶段)
  • 方法/属性装饰器:在类初始化时执行
  • 示例
    // 装饰器执行顺序演示
    function ClassDecorator() {
      console.log('类装饰器执行');
      return (target: any) => target;
    }
    
    function MethodDecorator() {
      console.log('方法装饰器工厂执行');
      return (
        target: any, 
        key: string, 
        descriptor: PropertyDescriptor
      ) => descriptor;
    }
    
    @ClassDecorator() // 输出:类装饰器执行
    class Demo {
      @MethodDecorator() // 输出:方法装饰器工厂执行(类定义时执行)
      method() {}
    }
    

2. 方法装饰器参数解析

function Validator(target: Object, key: string, descriptor: PropertyDescriptor) {
  // target: 类原型(如 User.prototype)
  // key: 方法名(如 'save')
  // descriptor: 包含 value(原方法)、writable 等属性的对象
  
  // 示例:重写方法实现参数校验
  const originalMethod = descriptor.value;
  descriptor.value = function(...args: any[]) {
    if (args[0] === undefined) throw new Error('参数不能为空');
    return originalMethod.apply(this, args);
  };
  return descriptor;
}

四、装饰器在框架中的实际应用

1. Vue3组合式API中的装饰器(需搭配Vue Decorators库)

import { Component, Prop, Watch } from 'vue-property-decorator';

@Component
export default class UserComponent {
  @Prop() userId!: number; // 声明props
  name = '张三';
  
  @Watch('userId')
  onUserIdChange(newVal: number) {
    console.log('用户ID变更:', newVal);
  }
  
  @Method('log') // 自定义装饰器示例
  logMessage() {
    console.log('组件日志');
  }
}

2. NestJS中的路由装饰器

import { Controller, Get, Post, Param } from '@nestjs/common';

@Controller('users') // 类装饰器定义路由前缀
export class UserController {
  @Get() // 方法装饰器定义GET请求
  findAll() { return '所有用户'; }
  
  @Post(':id') // 带参数的路由
  findOne(@Param('id') id: string) { return `用户 ${id}`; }
}

五、装饰器的实现原理与性能考量

1. 核心原理

  • 装饰器本质是元编程(Meta Programming) 的一种实现,通过操作类的元数据(Metadata)修改行为
  • 编译阶段会被转换为普通函数调用,例如:
    @Logger class User {} 
    // 等价于 
    class User {}
    Logger(User);
    

2. 性能注意事项

  • 装饰器执行于编译期,对运行时性能影响较小
  • 避免在装饰器中执行复杂逻辑(如API请求),可能导致加载延迟
  • 过多装饰器嵌套可能降低代码可维护性

六、问题

1. 问:装饰器与Mixin(混入)的区别?
    • 装饰器是行为增强,通过函数包装目标对象;
    • Mixin是代码复用,通过合并多个类的属性和方法生成新类。
    • 场景对比:
      • 装饰器适合“横切功能”(如日志、权限);
      • Mixin适合“垂直功能”(如多个组件共享表单逻辑)。
2. 问:如何实现一个带参数的装饰器?
    • 装饰器本身返回一个接收目标对象的函数(即“装饰器工厂”):
      function Permission(role: string) {
        return function(target: any, key: string, descriptor: PropertyDescriptor) {
          // 保存权限配置到元数据
          Reflect.defineMetadata('requiredRole', role, target, key);
          
          // 重写方法实现权限校验
          const originalMethod = descriptor.value;
          descriptor.value = function() {
            const userRole = getCurrentUserRole();
            if (userRole !== role) throw new Error('无权限');
            return originalMethod.apply(this, arguments);
          };
          return descriptor;
        };
      }
      
      // 使用示例
      class AdminPanel {
        @Permission('admin')
        deleteUser() {}
      }
      
3. 问:装饰器在TS与JS中的支持差异?
    • TS完全支持装饰器(需在tsconfig中开启experimentalDecoratorsemitDecoratorMetadata);
    • JS仅在ES7提案中支持,需通过Babel插件(如@babel/plugin-proposal-decorators)转换;
    • 两者核心语法一致,但TS通过Reflect.metadata提供更完善的元数据操作能力。

七、总结

“装饰器是一种通过高阶函数实现的代码增强模式,能在不修改原对象的前提下添加功能。它在框架中广泛用于路由定义、权限校验等场景,本质是编译期执行的元编程技术。使用时需注意装饰器类型(类/方法/属性/参数)的参数差异,以及TS与JS的语法兼容问题。在实际项目中,装饰器能有效减少代码耦合,提升可维护性,但需避免过度使用导致逻辑复杂。”