装饰器
目前(时间:2023-7-4;TS版本:5.1.6)装饰器(decorator)的特性还未正式成为js
的规范(以下内容均是stage2语法),所以即便现在ts
里面可以使用,但是还是作为实验性特性的功能 ,在未来的版本中可能会发生改变.
要启用对装饰器的实验性支持,你必须在命令行或tsconfig.json
中启用experimentalDecorators
编译器选项:
# Command Line:
tsc --target ES5 --experimentalDecorators
// tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
写法
装饰器Decorator
)是一种特殊的声明,可用于修改类、方法、属性或参数的行为。装饰器通过在被装饰元素的声明之前使用 @
符号来使用。可以多次应用装饰器,并且它们按照装饰器列表的顺序应用。
普通装饰器
直接将函数作为装饰器函数:
function ClassDecorator(target: any) {
// target ...
console.log(target)
}
@ClassDecorator
class TestClass {
constructor() { }
}
装饰器工厂函数
装饰器工厂函数,在调用时可以传递参数:
// 利用函数柯里化解决传参问题, 向装饰器传入一些参数,也可以叫 参数注解
function ClassDecorator(info: string) {
return (target: any) => {
// ...
}
}
@ClassDecorator('test')
class TestClass {
constructor() { }
}
类装饰器 Class Decorators
类装饰器:放置在类定义之前,作用于类声明上,有且仅有一个参数。该参数指的是类的构造函数(也可以说是类本身,因为ES6 class
的构造函数本来就等于 class
自身…)
class Test {
constructor() {
}
}
console.log(new Test().constructor === Test); // true
如果类装饰器的函数有返回值(新的构造函数),那么它将用返回的构造函数 来替换原有的 类的声明。
TS
内部声明的类装饰器的类型ClassDecorator
:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
示例1:防止类的实例被修改
const sealed:ClassDecorator = (constructor: Function) => {
// Object.seal()密封一个对象等价于阻止其扩展,可以改变属性的值,但无法修改或删除属性。
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("World!");
console.log(greeter.greet()) // OK: Hello, World!
Greeter.prototype.test = "Test"; // Error
示例2:用于添加一个静态方法和修改类的属性
<T extends { new (...args: any[]): any }>
定义一个泛型类型参数T
,该参数必须是具有构造函数类型的对象。其构造函数必须接受任意数量的参数并且返回一个对象(可以是任何类型)。
/**
* {new (...args: any[]): any } 可以改为 { new (...args: any[]): TestClass }
*/
const TestDeco = <T extends { new (...args: any[]): any }>(target: T) => {
// return class extends target { // 匿名类
return class Test extends target { // 类名Test
static addr:string = 'aaddr'
constructor(...args: any[]) {
super(...args)
// console.log('子类', this.name, Test.addr) // 用 `static` 定义的属性和方法,不可以通过 `this` 去访问,只能通过类名去调用
}
// 新增静态方法
newMethod() {
console.log('子类 newMethod', this.name, Test.addr);
}
// 修改类属性
sayAddr(){
console.log('修改后sayAddr log...');
}
}
}
// TestDeco2 这是和 TestDeco使用继承 不同的第二种写法
const TestDeco2 = (name: string): ClassDecorator => {
return (target: Function) => {
target.prototype.getNames = ():string => {
return name
}
}
}
@TestDeco
@TestDeco2('nameTest')
class TestClass {
static oldProp: string = 'OldProp'
constructor(public name: string) { }
sayAddr() {
console.log('sayAddr', this.name, TestClass.oldProp);
}
method() {
console.log('父类 method log...');
}
}
const test = new TestClass('Join')
test.sayAddr(); // 添加TestDeco前log:sayAddr Join OldProp; 添加后log:修改后sayAddr log...
test.method(); // 父类 method log...
(test as any).newMethod(); // 子类 newMethod Join aaddr
console.log((test as any).getNames()); // nameTest
在装饰器函数 TestDeco
中,<T extends { new (...args: any[]): any }>(target: T) => {...}
接受一个名为 target
的类构造函数对象,并返回一个新的类定义,该类定义必须继承 target
并具有新的扩展。
方法装饰器 Method Decorators
方法装饰器是在方法声明之前声明的。装饰器应用于方法的属性描述符,可以用于观察、修改或替换方法的定义。方法装饰器不能在声明文件、重载或任何其他环境上下文中使用(比如在声明类中)。
-
方法装饰器的表达式在运行时被作为函数调用,带有以下三个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名称。
- 成员的属性描述符。(如果脚本目标小于
ES5
,属性描述符将为undefined
)
-
如果方法装饰器返回一个值,它将被用作方法的属性描述符。(如果脚本目标小于
ES5
,则会忽略返回值) -
TS
内部声明的类装饰器的类型MethodDecorator
:declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
function testMethodDecorator(info: string): MethodDecorator {
return (target: any, methodName: any, descriptor: PropertyDescriptor) => {
console.log('1', target) // {constructor: ƒ, getTitle: ƒ}
console.log('2', methodName) // getTitle
console.log('3', descriptor) // {writable: true, enumerable: false, configurable: true, value: ƒ getTitle()}
}
}
class Demo {
title: string
constructor(msg: string) {
this.title = msg
}
@testMethodDecorator('TestDecorator')
getTitle() { }
}
示例1:使用方法装饰器来记录类的方法调用次数
function methodCount(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
methodCountMap[key] = methodCountMap[key] ? methodCountMap[key] + 1 : 1;
// 调用原始方法
const result = originalMethod.apply(this, args)
return result
}
return descriptor;
}
const methodCountMap: { [key: string]: number } = {}
class UserService {
private userList: string[] = []
constructor() { }
@methodCount
addUser(name: string) {
console.log('Adding user:', name);
this.userList.push(name)
console.log('用户添加成功!');
}
@methodCount
getUserList() {
console.log('User list:', this.userList);
}
}
const userService = new UserService();
userService.addUser('Join'); // 输出:Adding user: Join // 用户添加成功!
userService.addUser('Jack'); // 输出:Adding user: Jack // 用户添加成功!
userService.getUserList(); // User list: (2) ['Join', 'Jack']
console.log('methodCountMap', methodCountMap); // {addUser: 2, getUserList: 1}
示例2: 实现一个GET请求
import axios from 'axios';
const Get = (url: string): MethodDecorator => {
return (target, key, descriptor: PropertyDescriptor) => {
const fnc = descriptor.value;
axios.get(url).then(res => {
fnc(res, {
status: 200,
})
}).catch(e => {
fnc(e, {
status: 500,
})
})
}
}
//定义控制器
class Controller {
constructor() {
}
@Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
getList(res: any, status: any) {
console.log(res.data.result.list, status)
}
}
扩展:使用target
和descriptor
的区别
- 使用
descriptor
可以对方法的属性描述符修改。属性描述符定义了方法的特性和行为,用于修改或定义类方法的属性(包括属性的可访问性(writable
)、属性的值(value
)、属性是否可枚举(enumerable
)还有get / set
等等)。
- 例如示例1代码,在
addUser
和getUserList
方法上应用了methodCount
方法装饰器。descriptor
对象的修改允许我们在这两个方法被调用时,记录日志,且不会修改原始方法。
- 使用
target
可以直接在原型对象上修改方法的函数体。不推荐使用原因:可能会覆盖原始方法的实现,并影响程序的正常运行。
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = target[key];
target[key] = function (...args: any[]) {
const result = originalMethod.apply(this, args)
return result
}
}
class UserService {
constructor(private users: string[] = []) {}
@Log
public getUserList(): string[] {
return this.users;
}
@Log
public addUser(user: string) {
this.users.push(user);
}
}
const userService = new UserService(['Alice', 'Bob']);
console.log(userService.getUserList()); // ["Alice", "Bob"]
userService.addUser('Charlie');
console.log(userService.getUserList()); // ["Alice", "Bob", "Charlie"]
在上述代码中,使用 target
直接替换了 getUserList
和 addUser
方法,并将其覆盖到一个新的函数体。这允许我们在这些方法被调用时记录日志。注意,使用 target
直接替换函数体并不建议使用,因为它可能会覆盖原始方法的实现,并影响程序的正常运行。
为何使用originalMethod.apply(this, args)
-
- 通过使用
apply
,可以确保在调用原始方法时保持正确的定义的上下文和参数。
- 通过使用
-
- 若不使用它来调用原始方法,而是直接使用
originalMethod()
,则会导致以下问题:
-
上下文丢失:如果不使用
apply
而直接使用originalMethod()
,可能会导致原始方法内部的this
关键字丢失对上下文的引用。使用apply
可以确保引用正确的上下文(即类的实例)。 -
参数传递不正确:如果原始方法带参数,并且我们不使用
apply
方法传递参数,那么调用originalMethod()
时将无法正确传递参数。使用apply
才可以正确传递。
- 若不使用它来调用原始方法,而是直接使用
不使用apply
示例代码:
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// const result = originalMethod.apply(this, args)
const result = originalMethod(args[0], args[1], args[2]);
return result;
}
return descriptor;
}
class UserService {
constructor(private users: string[] = []) { }
@log
public getUserList(): string[] {
return this.users;
}
@log
public addUser(user: string) {
this.users.push(user);
}
}
const userService = new UserService(['Alice', 'Bob']);
console.log(12, userService.getUserList()); // ["Alice", "Bob"]
userService.addUser('Charlie');
console.log(13, userService.getUserList()); // ["Alice", "Bob", "Charlie"]
在上述代码中,我们在 log
方法装饰器中不使用 apply
,而是手动传递所有方法参数。这种方法可能会有以下问题:
- 参数个数和顺序要保持一致
由于方法的参数的数量和顺序可能会改变,当使用一个硬编码的方式调用函数时,必须按照固定的参数数量和顺序来调用函数。如果代码中使用了 rest
参数语法,或者在调用方法的时候修改了方法的参数个数和顺序,则这种方法可能会带来一些麻烦和错误。
- 难以维护
当被修饰的方法的参数列表变更时,除了在方法体中手动调整方法调用时的参数,还需要在装饰器中也要相应做出改变。这会导致代码更加难以维护和扩展。
rest
参数语法一种ES6
新增的语法,它允许函数接受不定数量的参数,并将它们转换为一个数组,语法格式如下:function functionName(...args: argType[]) { // function body }
属性装饰器 Property Decorators
属性装饰器在属性声明之前声明,属性装饰器表达式会在运行时当做函数被调用。带有以下两个参数:
-
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
-
成员的名称。
注意:由于 TypeScript
中原型成员定义时没有一种描述实例属性的机制,也没有一种观察或修改属性初始化器的方法,因此属性装饰器不能将 属性描述符 作为参数提供。此外,返回值也会被忽略。因此,属性装饰器只能用于观察已为类声明了特定名称的属性。
TS
内部声明的类装饰器的类型PropertyDecorator
:
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
示例1: 格式化时间戳
import 'reflect-metadata';
import dayjs from 'dayjs';
// 时间戳转日期格式装饰器
function DateFormat(format: string = 'YYYY-MM-DD HH:mm:ss') {
return (target: any, propertyKey: string) => {
let val = Reflect.get(target, propertyKey);
Reflect.set(target, propertyKey, {
get: () => {
return dayjs.unix(val).format(format);
},
set: (value: any) => {
val = value;
},
enumerable: true,
configurable: true
});
}
}
class MyClass {
@DateFormat('YYYY-MM-DD')
public timer!: number;
}
let myClass = new MyClass();
myClass.timer = Date.now() / 1000; // 注意,dayjs的unix方法需要的是秒,而Date.now()返回的是毫秒,所以这里要进行转换
console.log(myClass.timer); // 打印当前日期 YYYY-MM-DD
不使用js
库:
// 时间戳转日期格式装饰器
function DateFormat(format: string = 'yyyy-mm-dd') {
return function DateFormatDecorator(target: any, propertyKey: string) {
let value = target[propertyKey];
const getter = () => {
const date = new Date(value);
return date.getFullYear()
+ '-' + ('0' + (date.getMonth() + 1)).slice(-2)
+ '-' + ('0' + date.getDate()).slice(-2)
+ ' ' + ('0' + date.getHours()).slice(-2)
+ ':' + ('0' + date.getMinutes()).slice(-2)
};
const setter = (newVal: number) => {
value = newVal;
};
if (delete target[propertyKey]) {
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
}
class MyClass {
@DateFormat('yyyy-mm-dd') public timer!: number;
}
let myClass = new MyClass();
myClass.timer = Date.now()
console.log(myClass.timer); // 打印当前日期 yyyy-mm-dd hh:mm
示例1: 判断属性值是否符合范围
function ageRange(min: number, max: number) {
return (target: any, propertyKey: string): void => {
let value = target[propertyKey]
const getter = (): any => value
const setter = (next: number): void => {
if (next < min || next > max) {
// throw new Error(`age应该在 ${min} 到 ${max} 的范围内`);
console.log(`age应该在 ${min} 到 ${max} 的范围内`)
value = null
}else{
value = next;
}
}
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
})
}
}
class ExampleClass {
@ageRange(18, 35)
public age: number
constructor(age: number){
this.age = age
}
}
const userAge = new ExampleClass(20)
console.log(userAge.age); // 20
userAge.age = 40
console.log(userAge.age); // null // "age应该在 18 到 35 的范围内"
参数装饰器 Parameter Decorators
参数装饰器是定义在方法参数上的装饰器;能在方法,构造函数,或者方法的 setter/getter
上使用;参数装饰器可以被用于扩展或修改函数、类或者方法中传入的参数的行为。
当一个参数装饰器被应用到参数时,它将被调用时间是在运行时,而且带有以下三个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名称。
- 参数在函数参数列表中的索引。
注意,参数装饰器没有返回值。
TS
内部声明的类装饰器的类型ParameterDecorator
:
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
基础示例
function logParameter(target: any, proKey: string, parameterIndex: number){
console.log(target); // {constructor: ƒ, print: ƒ}
console.log(proKey); // print
console.log(parameterIndex); // 0
}
class MyClass{
print(@logParameter name: string){
console.log('PrintLog:', name);
}
}
let test = new MyClass()
test.print('7/21')
检查参数是否为空
参数装饰器主要用于注入元数据。但可以借助方法装饰器来辅助实现参数检查的功能。
// 导入`reflect-metadata` 包来启用装饰器的元编程能力。这个库增加了在`JavaScript`中获取类型信息的能力。
import 'reflect-metadata';
// 定义一个全局唯一的Symbol作为元数据的键。
const requiredMetadataKey = Symbol("required");
// validate 则用来在调用方法时读取元数据,并检查必要的参数是否为空。若为空,则抛出错误。否则,调用原方法。
function validate(target: any, propertyKey: any, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: string[]) {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey)
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] === null || arguments[parameterIndex] === undefined) {
throw new Error("'参数不能为空");
}
}
return originalMethod.apply(this, args)
}
}
}
// required 用来标记需要检查非空的参数,并将这些参数的索引存储在元数据中;
function required(target: any, propertyKey: string, paramIdx: number) {
let existingRequiredParameter: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameter.push(paramIdx)
Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameter, target, propertyKey)
}
class MyClass {
@validate
greet(@required name: string) {
console.log('GreetLog:', name);
}
}
const obj = new MyClass();
obj.greet('John'); // 输出: GreetLog: John
obj.greet(null); // 抛出错误: Uncaught (in promise) Error: '参数不能为空
当 greet
方法被调用时,validate
方法装饰器将执行,它读取元数据,检查哪些参数被 required
标记为需要检查。然后,validate
检查这些参数是否为空,如果为空,将抛出错误。如果所有的参数都不为空,那么将正常执行原始的方法。
访问符装饰器 Accessor Decorators
访问符装饰器 应用于存取器的声明,也就是 getter/setter
上。
访问符装饰器的类型和语法基本与 方法装饰器 一致
访问符装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
访问符装饰器可以返回一个属性描述符用来替换原来的描述符。
- 如果访问符装饰器返回一个新的描述符,系统将使用该描述符来修改类的属性。
- 如果访问符装饰器没有返回值,系统将使用原有的描述符。
注意:不能向多个同名的
get/set
访问器应用修饰器
示例
一般使用场景:只想在getter / setter
时机做一些事情
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
}
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value; // 配置类的属性访问器的configurable值,使得它们的属性描述符不可被修改。
};
}
装饰器执行顺序
- 属性方法先执行,谁先写 先执行谁
- 方法的时候, 先参数再方法,而且一定会在一起
- 最后是类
- 如果同类型,先执行离类近的
function classDecorator(target: any) {
console.log('类装饰器-执行');
}
function paramterDecorator(target: any, propertyKey: any, parameterIndex: number) {
console.log('(构造器中的)参数装饰器-执行');
}
function firstParamterDecorator(target: any, propertyKey: string, parameterIndex: number) {
console.log('第一个(方法的)参数装饰器-执行');
}
function secondParamterDecorator(target: any, propertyKey: string, parameterIndex: number) {
console.log('第二个(方法的)参数装饰器-执行');
}
function properDecorator(target: any, propertyKey: string) {
console.log('属性装饰器-执行');
}
function firstMethodDecorator(target: any, methodName: any, descriptor: PropertyDescriptor) {
console.log('第一个方法装饰器-执行');
}
function secondMethodDecorator(target: any, methodName: any, descriptor: PropertyDescriptor) {
console.log('第二个方法装饰器-执行');
}
@classDecorator
class Test {
constructor(@paramterDecorator age: number) { }
@firstMethodDecorator
say(@firstParamterDecorator pname: string) { }
@properDecorator
public name: string = 'Jack'
@secondMethodDecorator
print(@secondParamterDecorator addr: string) { }
}
const test = new Test(20)
test.say('sayName')
test.print('printAddr')
/**
* 执行结果:
* 第一个(方法的)参数装饰器-执行
* 第一个方法装饰器-执行
* 属性装饰器-执行
* 第二个(方法的)参数装饰器-执行
* 第二个方法装饰器-执行
* (构造器中的)参数装饰器-执行
* 类装饰器-执行
*/
由执行结果可以看出在定义的应用函数装饰器中,方法参数装饰器执行的优先级最高,而方法和属性装饰器受到定义顺序的影响。类与构造器参数装饰器优先级最低。
扩展:reflect-metadata
Reflect Metadata
是ES7
的一个提案,它主要用来在声明的时候添加和读取元数据。
reflect-metadata
是一个用于 TypeScript
的库,它提供了一组用于元数据(Metadata
)的API
。元数据是关于代码结构、类型和逻辑的描述性信息,可以用于运行时的反射和元编程,帮助我们在 TypeScript
中对类、成员属性和方法等进行注解和装饰。
安装:npm install reflect-metadata —save
要使用它,首先要安装它的依赖项,并在 TypeScript
的配置文件中启用 experimentalDecorators
和 emitDecoratorMetadata
选项,以支持装饰器和元数据的使用。
以下是这个库的index.d.ts
,可以看出该库没有任何导出, 再往下是对global
和命名空间的声明, 于是我们在项目中直接使用import 'reflect-metadata'
引入后便可在项目中访问到.
export { };
declare global {
namespace Reflect { }
}
Reflect.metadata(metadataKey, metadataValue)
-
作用:用于为类的属性或方法添加元数据
// 参数类型: function metadata(metadataKey: any, metadataValue: any): { (target: Function): void; (target: Object, propertyKey: string | symbol): void; }; /** * @param metadataKey: any 元数据的键(可以是任何有效的JavaScript值) * @param metadataValue: any 元数据的值(可以是任何有效的JavaScript值) * @returns (装饰器函数)没有显式的返回值,它会将元数据关联到相应的属性或方法。 */
-
示例:
// 定义元数据 const MyMetadataKey = Symbol('myMetadata'); // 1.类元数据 @Reflect.metadata('K1class', 'classMetaVal') class MyClass { // 2.属性元数据 @Reflect.metadata(MyMetadataKey, 'Hello, metadata!') public myProperty: string = 'test'; // 3.方法元数据 @Reflect.metadata('K3Method', 'Metadata for myMethod') myMethod() { console.log('myMethod'); } // 4.静态方法元数据 @Reflect.metadata('K4static', 'sayHello') static sayHello() { console.log('hello static'); } } const classVal = Reflect.getMetadata('K1class', MyClass) console.log(11, classVal); // 11 'classMetaVal' const test = new MyClass() const propVal = Reflect.getMetadata(MyMetadataKey, test, 'myProperty') console.log(22, propVal); // 22 'Hello, metadata!' const methodVal = Reflect.getMetadata('K3Method', test, 'myMethod') console.log(33, methodVal); // 33 'Metadata for myMethod' const staticVal = Reflect.getMetadata('K4static', MyClass, 'sayHello') console.log(44, staticVal); // 44 'sayHello'
Reflect.defineMetadata(metadataKey, metadataValue, target [, propertyKey])
-
作用:用于在给定的目标(
target
)或目标的特定属性上定义元数据。function defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey?: string | symbol): void; /** * @param target:表示要定义元数据的目标(类或对象)。 * @param propertyKey(可选):类的属性名或方法名,用于定义特定成员的元数据。 */
-
示例
import 'reflect-metadata'; // 定义元数据 const myMeta = Symbol('myMetadata'); class MyClass { static staticProperty: string = 'test'; myMethod() { console.log('myMethod'); } static sayHello() { console.log('hello static'); } } // 装饰 Reflect.defineMetadata(myMeta, 'classAAA', MyClass) const clasMeta = Reflect.getMetadata(myMeta, MyClass) // log: 'classAAA' // Reflect.defineMetadata(myMeta, 'staticSayHelloBBB', MyClass.sayHello) // const staticMethodMeta = Reflect.getMetadata(myMeta, MyClass.sayHello) // log: 'staticSayHelloBBB' /** * 两种写法,不能混用;即通过 MyClass.sayHello 装饰的,也只能通过 MyClass.sayHello 取。 * 写法二如下: */ Reflect.defineMetadata(myMeta, 'staticSayHelloBBB', MyClass, 'sayHello') const staticMethodMeta = Reflect.getMetadata(myMeta, MyClass, 'sayHello') // log: 'staticSayHelloBBB' Reflect.defineMetadata(myMeta, 'methodCCC', MyClass.prototype.myMethod) const methodMeta = Reflect.getMetadata(myMeta, MyClass.prototype.myMethod) // log: 'methodCCC' Reflect.defineMetadata(myMeta, 'staticPropertyDDDD', MyClass, 'staticProperty') const staticPropMeta = Reflect.getMetadata(myMeta, MyClass, 'staticProperty') // log: 'staticPropertyDDDD'
-
metadata
和defineMetadata
区别:-
Reflect.defineMetadata
是一个API
方法,用于在特定的目标上定义元数据,通过手动调用API
来实现。 -
Reflect.metadata
是一个装饰器函数,可以直接应用于类或类成员,通过装饰器语法来定义元数据。 -
Reflect.metadata
内部实际上调用了Reflect.defineMetadata
方法来定义元数据。 -
使用
Reflect.defineMetadata
直接定义元数据时,需要指定目标和(可选)属性; -
而使用
Reflect.metadata
装饰器时,装饰器会自动将所应用的目标和属性作为参数传递给Reflect.defineMetadata
方法。
在使用上的选择,可以根据具体情况来决定使用
API
方法还是装饰器函数,一般而言,使用装饰器语法更加简洁和直观,所以在可行的情况下推荐使用Reflect.metadata
装饰器。 -
hasMetadata / hasOwnMetadata
-
方法签名:
-
Reflect.hasMetadata(metadataKey, target [, propertyKey])
-
Reflect.hasOwnMetadata(metadataKey, target [, propertyKey])
// hasMetadata function hasMetadata(metadataKey: any, target: Object, propertyKey?: string | symbol): boolean; // hasOwnMetadata function hasOwnMetadata(metadataKey: any, target: Object, propertyKey?: string | symbol): boolean; /** * @param metadataKey 表示要检查的元数据键。 * @param target 表示要检查元数据的目标(类或对象) * @param propertyKey(可选):表示要检查元数据的目标的特定属性(仅适用于类成员的元数据)。 * @returns 一个布尔值,表示元数据键是否存在于指定目标上。 */
-
-
作用和区别
-
hasMetadata
:检查给定的元数据键(metadataKey
)是否存在于指定的目标上。 -
hasOwnMetadata
: 检查给定的元数据键(metadataKey
)是否存在于指定的目标自身,即仅判断目标自身是否具有该元数据。(只查找对象上的元数据, 而不会继续向上查找原型链上的)
import 'reflect-metadata'; // 定义元数据 const symbolMeta = Symbol('myMetadata'); class MyClass { @Reflect.metadata(symbolMeta, 'myName') static myName: string = 'Join' } Reflect.defineMetadata(symbolMeta, 'Hello', MyClass); const t1 = Reflect.hasMetadata(symbolMeta, MyClass); // log: true const t2 = Reflect.hasMetadata(symbolMeta, MyClass, 'myName'); // log: true const t3 = Reflect.hasOwnMetadata(symbolMeta, MyClass, 'myName'); // log: true
-
getMetadata / getOwnMetadata
-
方法签名:
-
Reflect.getMetadata(metadataKey, target [, propertyKey])
-
Reflect.getOwnMetadata(metadataKey, target [, propertyKey])
// getMetadata function getMetadata(metadataKey: any, target: Object, propertyKey?: string | symbol): any; // getOwnMetadata function getOwnMetadata(metadataKey: any, target: Object, propertyKey?: string | symbol): any; /** * @param metadataKey 表示要检查的元数据键。 * @param target 表示要检查元数据的目标(类或对象) * @param propertyKey(可选):表示要检查元数据的目标的特定属性(仅适用于类成员的元数据)。 * @returns 表示指定元数据键的值,如果元数据不存在则返回undefined。 */
-
-
作用和区别
-
getMetadata
: 从指定的目标上获取给定的元数据键(metadataKey
)的值。 -
getOwnMetadata
: 从指定的目标自身获取给定的元数据键(metadataKey
)的值,即获取目标自身具有的元数据。(只查找对象上的元数据, 而不会继续向上查找原型链上的)
class MyClass { @Reflect.metadata('key', 'value') myMethod() { // ... } } // const metadata = Reflect.getMetadata('key', MyClass.prototype, 'myMethod'); const metadata = Reflect.getOwnMetadata('key', MyClass.prototype, 'myMethod');
-
getMetadataKeys / getOwnMetadataKeys
-
方法签名:
-
Reflect.getMetadataKeys(target [, propertyKey])
-
Reflect.getOwnMetadataKeys(target [, propertyKey])
// getMetadataKeys function getMetadataKeys(target: Object, propertyKey?: string | symbol): any[]; // getOwnMetadataKeys function getOwnMetadataKeys(target: Object, propertyKey?: string | symbol): any[]; /** * @param target 表示要检查元数据的目标(类或对象) * @param propertyKey (可选)用于获取指定属性的元数据键。在类的方法装饰器中使用时,可以指定具体的方法名称来获取该方法的元数据键。 * @returns 返回类型是一个数组,包含元数据的键。 */
-
-
作用和区别
-
这两个方法会返回指定对象 (
target
) 或其原型链上的所有元数据的键。可选的第二个参数propertyKey
用于获取指定属性的元数据键。 -
区别和别的
...Own...
方法一样:getOwnMetadataKeys
只会对象自身的元数据,而不会检查原型链上的对象。
class MyClass { @Reflect.metadata('key1', 'value1') @Reflect.metadata('key2', 'value2') myMethod() { // ... } } const metadataKeys = Reflect.getMetadataKeys(MyClass.prototype, 'myMethod'); // ["key1", "key2"] const ownMetadataKeys = Reflect.getOwnMetadataKeys(MyClass.prototype, 'myMethod'); // ["key1", "key2"]
-
Reflect.deleteMetadata(metadataKey, target, propertyKey)
-
作用:删除对象中存储的指定元数据。
function deleteMetadata(metadataKey: any, target: Object, propertyKey?: string | symbol): boolean; /** * @param metadataKey 要删除的元数据的键 * @param target 目标对象,要删除元数据的对象。可以是类的原型对象或普通对象。 * @param propertyKey (可选)用于删除指定属性的元数据。在类的方法装饰器中使用时,可以指定具体的方法名称来删除该方法的元数据。 * @returns 返回类型是一个数组,包含元数据的键。 */
-
示例:
import 'reflect-metadata'; // 定义元数据 const symbolMeta = Symbol('myMetadata'); @Reflect.metadata(symbolMeta, 'value1') class MyClass { @Reflect.metadata(symbolMeta, 'value2') myMethod() { // ... } @Reflect.metadata(symbolMeta, 'value3') static staticPerty: string = 'join' } const t1 = Reflect.deleteMetadata(symbolMeta, MyClass) const t2 = Reflect.deleteMetadata(symbolMeta, MyClass.prototype, 'myMethod') const t3 = Reflect.deleteMetadata(symbolMeta, MyClass, 'staticPerty') console.log(t1, t2, t3); // true true true
Reflect.decorate(decorators, target)
Reflect.decorate()
装饰:接受一个装饰器的集合(Decorator[]
)给目标对象(可以是类、属性、方法)
-
1.对类的装饰:
Reflect.decorate(decorators: ClassDecorator[], target: Function)
,返回应用提供的装饰器后的值。(注意:装饰器应用是与array
的位置方向相反, 为从右往左) -
2.对属性或方法装饰:
Reflect.decorate(decorators:(PropertyDecorator | MethodDecorator)[], target: Object, targetKey: string | symbol, descriptor?:PropertyDescriptor): PropertyDescriptor
/**
* 参数说明:
* @decorators 一个装饰器数组,通过这个参数可以将多个装饰器应用到同一个目标上
* @target 目标对象。目标对象可以是类的原型对象,用于装饰类的实例属性和方法;也是类本身,用于装饰静态成员;还可以是类的实例方法的上下文,用于装饰方法参数。
* @propertyKey 要装饰的属性名称 key
* @descriptor 被装饰的属性的描述 descriptor
*/
import 'reflect-metadata';
/******************* 分隔符 是示例一 ******************************************************/
const classDecorator: ClassDecorator = target => {
target.prototype.sayName = () => console.log('装饰类');
}
// 应用对类的装饰
class MyClass {
message: string;
constructor(msg: string) {
this.message = msg;
}
sayName() {
console.log(this.message)
}
}
Reflect.decorate([classDecorator], MyClass) // 对其进行装饰
const obj = new MyClass('Hello World');
// 调用装饰过的方法
obj.sayName(); // 装饰类
/******************* 分隔符 是示例二 ******************************************************/
// 属性装饰
const decoratePropertyDecorator = (target: any, propertyKey: string | symbol) => {
const value = target[propertyKey]
target[propertyKey] = () => {
console.log('覆盖....')
// value.call(target)
}
}
// 方法装饰器
const decorateMethodDecorator = (target: any, propertyKey: string | symbol, descriptor: any) => {
// 将其描述改为不可编辑
descriptor.configurable = false
descriptor.writable = false
return descriptor
}
// 应用装饰器
class MyClass2 {
static staticProperty() {
console.log('MyClass2 staticProperty');
};
sayName() {
console.log('MyClass2 sayName')
}
}
/******* PropertyDecorator TEST *******/
Reflect.decorate([decoratePropertyDecorator], MyClass2, 'staticProperty')
// 测试装饰属性是否正确
MyClass2.staticProperty() //覆盖....
/******* MethodDecorator TEST *******/
// 获取原descriptor:
let orginDescriptor = Object.getOwnPropertyDescriptor(MyClass2.prototype, 'sayName')
// 获取修改后的descriptor:
let updatedDescriptor = Reflect.decorate([decorateMethodDecorator], MyClass2, 'sayName', orginDescriptor)
// 将修改后的descriptor添加到对应的方法上
Object.defineProperty(MyClass2.prototype, 'sayName', updatedDescriptor)
// 测试:
const obj = new MyClass2();
obj.sayName = () => console.log('Test UpdateSayName'); // TypeError: Cannot assign to read only property 'sayName' of object '#<MyClass2>'
// 验证正确(因为在decorateMethodDecorator将其描述改为“不可编辑”)