Skip to content

属性装饰器

属性装饰器(Property Decorator) 用于修饰类的属性声明。与类装饰器不同,属性装饰器无法直接修改属性的描述符,也无法获取属性的初始值,它的核心作用通常是 收集元数据

1. 装饰器定义与参数

属性装饰器是一个接收两个参数的函数:

参数说明

  1. target:
    • 对于 实例属性:指向类的 原型对象(Prototype)
    • 对于 静态属性:指向类的 构造函数(Constructor)
  2. propertyKey: 字符串,表示被装饰的 属性名

1.1 基础示例

typescript
function logProperty(target: any, key: string) {
  console.log('目标对象:', target);
  console.log('属性名:', key);
  // 验证是否为原型
  console.log('是否为原型:', target === A.prototype);
}

class A {
  @logProperty
  name: string;
}

2. 装饰器工厂与传值

属性装饰器同样支持工厂模式,这使得我们可以向装饰器传递自定义参数。

typescript
function defaultValue(value: string) {
  return function (target: any, key: string) {
    // 尝试在原型上设置默认值
    target[key] = value;
  };
}

class User {
  @defaultValue('匿名用户')
  nickname: string;
}

const u = new User();
console.log(u.nickname); // 输出: "匿名用户"

3. 核心机制与陷阱

重要提示:原型共享问题

在属性装饰器中,target 指向的是类的 原型(针对实例属性)。这意味着如果你通过 target[key] = value 赋值,该值是保存在原型上的,所有实例将共享同一个值

3.1 静态属性 vs 实例属性

typescript
function checkTarget(target: any, key: string) {
  console.log(`${key} 的 target 是:`, target.name || 'Prototype');
}

class Test {
  @checkTarget
  instanceProp: string; // target 为 Test.prototype

  @checkTarget
  static staticProp: string; // target 为 Test 构造函数
}

4. 进阶:模拟实例初始化

由于属性装饰器无法直接访问实例(它在类定义时执行),如果需要为每个实例设置独立的初始值,通常需要配合构造函数或利用“元数据收集+统一初始化”的策略。

查看模拟实例初始化的实现方案

这种方案通过在原型上暂存属性规则,并在构造函数中统一调用的方式来实现。

typescript
function initValue(value: any) {
  return function (target: any, key: string) {
    // 1. 初始化存储空间
    if (!target.__props) {
      target.__props = {};
    }
    // 2. 记录属性与对应的值
    target.__props[key] = value;

    // 3. 定义统一的初始化方法
    if (!target.__initProperties) {
      target.__initProperties = function () {
        for (const prop in target.__props) {
          // this 指向当前的实例
          this[prop] = target.__props[prop];
        }
      };
    }
  };
}

class Person {
  @initValue('张三')
  name: string;

  @initValue(18)
  age: number;

  constructor() {
    // 在构造函数中手动触发初始化逻辑
    if (typeof (this as any).__initProperties === 'function') {
      (this as any).__initProperties();
    }
  }
}

const p1 = new Person();
const p2 = new Person();
p2.name = '李四';

console.log(p1.name); // 张三
console.log(p2.name); // 李四 (互不影响)

5. 总结

  • Target 规律:实例属性看原型,静态属性看构造函数。
  • 局限性:无法拦截属性的读写(这是 访问器装饰器 的职责),无法直接操作实例。
  • 典型场景:配合 reflect-metadata 库进行依赖注入、数据校验规则的声明。