Appearance
属性装饰器
属性装饰器(Property Decorator) 用于修饰类的属性声明。与类装饰器不同,属性装饰器无法直接修改属性的描述符,也无法获取属性的初始值,它的核心作用通常是 收集元数据。
1. 装饰器定义与参数
属性装饰器是一个接收两个参数的函数:
参数说明
target:- 对于 实例属性:指向类的 原型对象(Prototype)。
- 对于 静态属性:指向类的 构造函数(Constructor)。
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库进行依赖注入、数据校验规则的声明。
