Skip to content

理解装饰器

装饰器(Decorator) 是面向对象编程中的核心概念之一。在许多成熟的面向对象语言中,装饰器早已存在,例如 Java 中的 注解(Annotation)C# 中的 特征(Attribute)

1. 背景与现状

装饰器并不是 TypeScript 的新发明,JavaScript 本身在 ES6 时期就提出了装饰器提案。然而,尽管经过了近 10 年的演进和多次规范重写,直到 2024 年,它才刚刚推进到 Stage 3 阶段。

现状说明

规范制定缓慢的原因在于 JavaScript 的运行环境极度多样化(浏览器、Node.js 等),规范必须兼顾各平台的执行效率与兼容性。

在现代前端生态中:

  • React 18Vue 3:更倾向于 函数式模块化 风格,这种风格天然支持 Tree Shaking
  • Angular:一直深度集成装饰器。
  • NestJS:将装饰器作为其架构的核心。

无论规范何时正式定稿,掌握装饰器理论对于理解程序设计模式和提升代码抽象能力都具有重要意义。

2. 装饰器模式

在软件工程中,装饰器模式 是一种结构型设计模式。它允许你通过将对象放入包含行为的特殊“包装器”中,动态地为对象添加新功能。

2.1 传统面向对象的实现

通过类继承和组合来实现装饰。

查看代码实现
javascript
// 组件接口
class TextMessage {
  constructor(message) {
    this.message = message;
  }

  getText() {
    return this.message;
  }
}

// 装饰器基类
class MessageDecorator {
  constructor(textMessage) {
    this.textMessage = textMessage;
  }

  getText() {
    return this.textMessage.getText();
  }
}

// 具体装饰器
class HTMLDecorator extends MessageDecorator {
  getText() {
    const msg = super.getText();
    return `<p>${msg}</p>`;
  }
}

class EncryptDecorator extends MessageDecorator {
  getText() {
    const msg = super.getText();
    // 加密逻辑
    return this.encrypt(msg);
  }
  encrypt(msg) {
    return msg.split('').reverse().join('');
  }
}

// 使用
let message = new TextMessage('Hello World');
message = new HTMLDecorator(message);
message = new EncryptDecorator(message);

console.log(message.getText()); // 输出加密的 HTML 格式文本

2.2 函数式(高阶函数)的实现

在 JavaScript 中,我们完全可以使用高阶函数替代类继承,实现同样的装饰效果,这种写法更加符合现代 JS 习惯。

查看代码实现
javascript
// 基础消息类
class TextMessage {
  constructor(message) {
    this.message = message;
  }

  getText() {
    return this.message;
  }
}

// 高阶函数 - HTML装饰器
function HtmlDecoratedClass(BaseClass) {
  return class extends BaseClass {
    getText() {
      const originalText = super.getText();
      return `<p>${originalText}</p>`;
    }
  };
}

// 高阶函数 - 加密装饰器
function EncryptDecoratedClass(BaseClass) {
  return class extends BaseClass {
    getText() {
      const originalText = super.getText();
      return this.encrypt(originalText);
    }
    encrypt(msg) {
      return msg.split('').reverse().join('');
    }
  };
}

// 使用装饰器
let DecoratedClass = HtmlDecoratedClass(TextMessage);
DecoratedClass = EncryptDecoratedClass(DecoratedClass);

const messageInstance = new DecoratedClass('Hello World');
console.log(messageInstance.getText()); // 输出被 HTML 格式化并加密的文本

3. 为什么需要装饰器?

虽然设计模式能解决一部分问题,但在实际开发(如数据校验)中,我们面临着更细粒度的需求。

3.1 场景引入:用户数据校验

假设我们有一个 User 类,需要对属性进行约束:

typescript
class User {
  loginId: string; // 需求:必须是3-5个字符
  loginPwd: string; // 需求:必须是6-12个字符
  age: number; // 需求:必须是0-100之间的数字
  gender: '男' | '女';
}

传统方案的问题

  1. 关注点分离(SoC)失效:校验逻辑往往被剥离到外部(如 validateUser 函数),导致属性定义与其约束规则分离。
  2. 代码重复:不同的类可能有相似的约束逻辑,导致校验代码冗余。

3.2 理想方案:声明式装饰

如果能在定义属性的同时,“顺便”声明它的约束规则,代码将变得极其直观:

typescript
// 伪代码示例
class User {
  @required
  @range(3, 5)
  @description('账号')
  loginId: string;

  @required
  @range(6, 12)
  @description('密码')
  loginPwd: string;
}

4. 装饰器的核心:元数据

装饰器的核心作用是:为类、方法、属性或参数提供元数据信息。

4.1 什么是元数据?

元数据(Metadata) 即“描述数据的数据”。

例子

我们在 HTML 中早已见过元数据的应用,即 <meta> 标签:

html
<!-- 描述文档编码 -->
<meta charset="UTF-8" />
<!-- 描述视口尺寸 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

5. 总结

装饰器带来的核心价值在于:

  • 关注点统一:在定义数据的同时定义规则。
  • 高度抽象:将通用的逻辑(校验、日志、权限)封装为可复用的标签(Annotation)。
  • 代码整洁:减少冗余的逻辑代码,使业务逻辑更加清晰。