**反射(Reflect)**一般指那些用来检验(inspect)其他代码的代码,部分使用场景:依赖注入/运行时类型断言/测试。

当JS应用变复杂时,需要使用一些(IoC容器)或特性(运行时类型断言)来处理复杂度,而由于JS中不存在反射的特性很难做到这一点。

一个反射API应允许在运行时检验一个未知对象,获取其不限于名称、类型、接口、参数等信息。

在JS中可以使用Object.getOwnPropertyDescriptor()Object.keys()来获取实体的信息,但还是需要反射去实现更强大的开发工具。目前在TS中可以使用reflect-metadata来实现反射特性,同时这个库是作为metadata reflection API的polyfill,可参考相关提案

Reflect Metadata 是的一个实验性接口,可以通过装饰器来给类添加一些自定义的信息。然后通过反射将这些信息提取出来。这个接口目前还不是 ECMAScript 标准的一部分,需要安装 reflect-metadata 垫片才能使用。

1
npm install reflect-metadata --save

或者

1
yarn add reflect-metadata

需要在全局的位置导入此模块,例如:入口文件。

1
import 'reflect-metadata';

当然你也可以通过反射来添加这些信息。

1
2
3
4
5
6
7
8
9
10
@Reflect.metadata('name', 'A')
class A {
@Reflect.metadata('hello', 'world')
public hello(): string {
return 'hello world'
}
}

Reflect.getMetadata('name', A) // 'A'
Reflect.getMetadata('hello', new A()) // 'world'

概念解释

  • metadataKey - 元数据的 Key,对于一个对象来说,他可以有很多元数据,每一个元数据都对应有一个 Key。
  • metadataValue - 元数据的值,可以是任意类型。
  • target - 表示要操作的元数据对象。
  • propertyKey - 表示要操作的元数据对象上某个属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 'name' 是metadataKey;'A'是metadataValue;
@Reflect.metadata('name', 'A')
class A {
// 'hello' 是metadataKey;'world'是metadataValue;
@Reflect.metadata('hello', 'world')
public hello(): string {
return 'hello world'
}
}

// ‘name’是metadataKey;A是target
Reflect.getMetadata('name', A) // 'A'
// ‘hello’是metadataKey; new A() 是target
Reflect.getMetadata('hello', new A()) // 'world'

接口说明

API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace Reflect {
// 用于装饰器
metadata(metadataKey, metadataValue): (target, propertyKey?) => void

// 在对象上面定义元数据
defineMetadata(metadataKey, metadataValue, target, propertyKey?): void

// 是否存在元数据
hasMetadata(metadataKey, target, propertyKey?): boolean
hasOwnMetadata(metadataKey, target, propertyKey?): boolean

// 获取元数据
getMetadata(metadataKey, target, propertyKey?): any
getOwnMetadata(metadataKey, target, propertyKey?): any

// 获取所有元数据的 Key
getMetadataKeys(target, propertyKey?): any[]
getOwnMetadataKeys(target, propertyKey?): any[]

// 删除元数据
deleteMetadata(metadataKey, target, propertyKey?): boolean
}

元数据接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 定义元数据
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

// 检查指定关键字的元数据是否存在,会遍历继承链
let result1 = Reflect.hasMetadata(metadataKey, target);
let result2 = Reflect.hasMetadata(metadataKey, target, propertyKey);

// 检查指定关键字的元数据是否存在,只判断自己的,不会遍历继承链
let result3 = Reflect.hasOwnMetadata(metadataKey, target);
let result4 = Reflect.hasOwnMetadata(metadataKey, target, propertyKey);

// 获取指定关键字的元数据值,会遍历继承链
let result5 = Reflect.getMetadata(metadataKey, target);
let result6 = Reflect.getMetadata(metadataKey, target, propertyKey);

// 获取指定关键字的元数据值,只查找自己的,不会遍历继承链
let result7 = Reflect.getOwnMetadata(metadataKey, target);
let result8 = Reflect.getOwnMetadata(metadataKey, target, propertyKey);

// 获取元数据的所有关键字,会遍历继承链
let result9 = Reflect.getMetadataKeys(target);
let result10 = Reflect.getMetadataKeys(target, propertyKey);

// 获取元数据的所有关键字,只获取自己的,不会遍历继承链
let result11 = Reflect.getOwnMetadataKeys(target);
let result12 = Reflect.getOwnMetadataKeys(target, propertyKey);

// 删除指定关键字的元数据
let result13 = Reflect.deleteMetadata(metadataKey, target);
let result14 = Reflect.deleteMetadata(metadataKey, target, propertyKey);

// 装饰器方式设置元数据
@Reflect.metadata(metadataKey, metadataValue)
class C {
@Reflect.metadata(metadataKey, metadataValue)
method() {
}
}

design 类型元数据

要使用 design 类型元数据需要在 tsconfig.json 中设置 emitDecoratorMetadata 为 true:

1
2
3
4
5
6
7
8
9
10
{
"compilerOptions": {

// 是否启用实验性的ES装饰器
"experimentalDecorators": true

// 是否自动设置design类型元数据(关键字有"design:type"、"design:paramtypes"、"design:returntype")
"emitDecoratorMetadata": true
}
}

emitDecoratorMetadata 设为 true 后,会自动设置 design 类型的元数据,通过以下方式可以获取元数据的值:

  1. design:type - 类型元信息
1
2
3
4
5
6
7
8
9
10
11
12
import 'reflec-metadata'
const logType = (value: string) => {
return function(target: any, propertyKey: string) => {
const type = Reflect.getMetadata("design:type", target, propertyKey);
console.log(`${value} - ${propertyKey} type: ${type.name}`);
}
}
class Foo {
@logType('Prop decorator')
public barProp: string;
}
// => Prop decorator - barProp type: String
  1. design:paramtypes - 参数类型元信息
1
2
3
4
5
6
7
8
9
10
11
import 'reflec-metadata'
const logParamTypes = (target: any, propertyKey: string, descriptor) => {
const types = Reflect.getMetadata("design:paramtypes", target, propertyKey);
const s = types.map(type => type.name).join(',')
console.log(`${propertyKey} param types: ${s}`);
}
class Foo {
@logParamTypes
barMethod(name: string, age: number) { }
}
// => barMethod param types: String,Number
  1. design:returntype - 返回值元信息
1
2
3
4
5
6
7
8
9
10
import 'reflec-metadata'
const logReturnTypes = (target: any, propertyKey: string, descriptor) => {
const type = Reflect.getMetadata("design:returntype", target, propertyKey);
console.log(`${propertyKey} return types: ${type.name}`);
}
class Foo {
@logReturnTypes
barMethod(age: number) : number { return age }
}
// => barMethod return types: Number

不同类型的装饰器获得的 design 类型的元数据值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const MyClassDecorator: ClassDecorator = (target: any) => {
const type = Reflect.getMetadata('design:type', target);
console.log(`类[${target.name}] design:type = ${type && type.name}`);

const paramTypes = Reflect.getMetadata('design:paramtypes', target);
console.log(`类[${target.name}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));

const returnType = Reflect.getMetadata('design:returntype', target)
console.log(`类[${target.name}] design:returntype = ${returnType && returnType.name}`);
};

const MyPropertyDecorator: PropertyDecorator = (target: any, key: string) => {
const type = Reflect.getMetadata('design:type', target, key);
console.log(`属性[${key}] design:type = ${type && type.name}`);

const paramTypes = Reflect.getMetadata('design:paramtypes', target, key);
console.log(`属性[${key}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));

const returnType = Reflect.getMetadata('design:returntype', target, key);
console.log(`属性[${key}] design:returntype = ${returnType && returnType.name}`);
};

const MyMethodDecorator: MethodDecorator = (target: any, key: string, descriptor: PropertyDescriptor) => {
const type = Reflect.getMetadata('design:type', target, key);
console.log(`方法[${key}] design:type = ${type && type.name}`);

const paramTypes = Reflect.getMetadata('design:paramtypes', target, key);
console.log(`方法[${key}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));

const returnType = Reflect.getMetadata('design:returntype', target, key)
console.log(`方法[${key}] design:returntype = ${returnType && returnType.name}`);
};

const MyParameterDecorator: ParameterDecorator = (target: any, key: string, paramIndex: number) => {
const type = Reflect.getMetadata('design:type', target, key);
console.log(`参数[${key} - ${paramIndex}] design:type = ${type && type.name}`);

const paramTypes = Reflect.getMetadata('design:paramtypes', target, key);
console.log(`参数[${key} - ${paramIndex}] design:paramtypes =`, paramTypes && paramTypes.map(item => item.name));

const returnType = Reflect.getMetadata('design:returntype', target, key)
console.log(`参数[${key} - ${paramIndex}] design:returntype = ${returnType && returnType.name}`);
};

@MyClassDecorator
class MyClass {
@MyPropertyDecorator
myProperty: string;

constructor (myProperty: string) {
this.myProperty = myProperty;
}

@MyMethodDecorator
myMethod (@MyParameterDecorator index: number, name: string): string {
return `${index} - ${name}`;
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
属性[myProperty] design:type = String
属性[myProperty] design:paramtypes = undefined
属性[myProperty] design:returntype = undefined
参数[myMethod - 0] design:type = Function
参数[myMethod - 0] design:paramtypes = [ 'Number', 'String' ]
参数[myMethod - 0] design:returntype = String
方法[myMethod] design:type = Function
方法[myMethod] design:paramtypes = [ 'Number', 'String' ]
方法[myMethod] design:returntype = String
类[MyClass] design:type = undefined
类[MyClass] design:paramtypes = [ 'String' ]
类[MyClass] design:returntype = undefined
*/

原型链查找

类似于类的继承,查找元数据的方式也是通过原型链进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
@Reflect.metadata('name', 'hello')
hello() {}
}

const t1 = new A()
const t2 = new A()
Reflect.defineMetadata('otherName', 'world', t2, 'hello')
Reflect.getMetadata('name', t1, 'hello') // 'hello'
Reflect.getMetadata('name', t2, 'hello') // 'hello'
Reflect.getMetadata('otherName', t2, 'hello') // 'world'

Reflect.getOwnMetadata('name', t2, 'hello') // undefined
Reflect.getOwnMetadata('otherName', t2, 'hello') // 'world'

Refer To

https://jkchao.github.io/typescript-book-chinese/tips/metadata.html#%E8%87%AA%E5%AE%9A%E4%B9%89-metadatakey

https://www.jianshu.com/p/653bce04db0b