Reflect Metadata(元数据)学习笔记

2,785 阅读7分钟

decorate方法

declare type ClassDecorator = <TFunction exntends Function>(target: TFunction) ⇒ TFunction | void;

function decorate(decorators: ClassDecorator[], target: Function): Function;

  • 对类的装饰

    对类的装饰该方法有几个参数, 分别是:

    • @param {Array} decorators - 装饰器的数组
    • @param {Object} target - 目标对象
    • @returns 返回应用提供的装饰器后的值
    • 注意: 装饰器应用是与array的位置方向相反, 为从右往左.

    来个🌰:给TestCkassDecorator类添加或者修改sayName方法

    const classDecorator: ClassDecorator = target => {
      target.prototype.sayName = () => console.log('veloma');
    	// return target; 这里可以return也可以不return, 因为target是一个对象引用
    }
    
    class TestClassDecorator {
    constructor(public name = '') {}
    sayName() {
            console.log(this.name);
    }
    }
    
    Reflect.decorate([classDecorator], TestClassDecorator); // 对类进行修饰
    const t = new TestClassDecorator('nihao');
    
    t.sayName(); // veloma
    

    注意:在classDecorator中传入的target, 只能修改其prototype的方法, 不能修改其属性, 因为其属性是ready-only

  • 对属性或方法的装饰

    对属性或方法的修饰有几个参数, 分别是:

    • @param {Array} decorators - 装饰器的集合
    • @param {Object} target - 目标对象
    • @param {string} key - 要装饰的属性名称
    • @param {Object} descriptor - 该属性的描述

    注意: descriptor分为两种, 一种是数据描述符, 一种是存取描述符

    // 数据描述符
    {
        value: 'aaa',
        configurable: true,
        writable: true,
        enumerable: true
    }
    // 存取描述符
    {
        get() {return 1},
        set() { console.log('set') },
        configurable: true,
        enumerable: true
    }
    

    属性装饰器: AOP编程, 在原方法的后面加上操作

    const propertyDecorator: PropertyDecorator = (target, propertyKey) => {
    const origin = target[propertyKey];
        target[propertyKey] = () => {
          origin.call(target);
          console.log('added override');
        }
    }
    
    class PropertyAndMethodExample {
        static staticProperty() {
            console.log('im static property');
        }
    
        method() {
            console.log('im one instance method');
        }
    }
    
    // 装饰PropertyAndMethodExample的staticProperty方法(静态方法)
    Reflect.decorate([propertyDecorator], PropertyAndMethodExample, 'staticProperty');
    PropertyAndMethodExample.staticProperty(); // im static property \n added override
    

    方法装饰器

    const methodDecorator: MethodDecorator = (target, propertyKey, descriptor) => {
    	// 将其描述改为不可编辑
    	descriptor.configurable = false;
    	descriptor.writable = false;
    	return descriptor;
    }
    
    class PropertyAndMethodExample {
        static staticProperty() {
            console.log('im static property');
        }
    
    	method() {
            console.log('im one instance method');
    	}
    }
    
    // 获取原descriptor
    let descriptor = Object.getOwnPropertyDescriptor(PropertyAndMethodExample.prototype, 'method');
    
    // 获取修改后的descriptor
    descriptor = Reflect.decorate([methodDecorator], PropertyAndMethodExample, 'method', descriptor);
    // 将修改后的descriptor添加到对应的方法上
    Reflect.defineProperty(PropertyAndMethodExample.prototype, 'method', descriptor);
    
    const example = new PropertyAndMethodExample();
    example.method = () => console.log('override'); // 报错: 因为已经将该方法(属性)的writable描述符设置为false
    

metadata方法

/**
* @param {string} metadataKey - 元数据入口的key
* @param {*} metadataValue 元数据入口的value
* @returns 装饰器函数
*/
function metadata(metadataKey: any, metadataValue: any) {
    (target: Function): void;
    (target: Object, propertyKey: string | symbol): void;
}

实例

const nameSymbol = Symbol('veloma');

// 类元数据
@Reflect.metadata('class', 'class');
class MetaDataClass {
    // 实例属性元数据
    @Reflect.metadata(nameSymbol, 'nihao')
    public name = 'origin';

    // 实例方法元数据
    @Reflect.metadata('getName', 'getName')
    public getName() {}

    // 静态方法元数据
    @Reflect.metadata('static', 'static')
    static staticMethod() {}
}

// 创建元数据类的实例
const metadataInstance = new MetaDataClass();

// 获取MetaDataClass的name元数据
const value = Reflect.getMetadata('name', MetaDataClass); // undefined
// 获取实例中name属性的nameSymbol元数据
const name = Reflect.getMetadata(nameSymbol, metadataInstance, 'name'); // nihao
// 获取实例中getName属性的getName元数据
const methodVal = Reflect.getMetadata('getName', metadataInstance, 'getName'); // getName
// 获取元数据类的staticMethod属性的static元数据
const staticVal = Reflect.getMetadata('static', MetaDataClass, 'staticMethod'); // static

console.log(value, name, methodVal, staticVal); // undefined nihao getName static

defineMetadata方法

该方法是metadata的定义版本, 也就是非@版本, 会多穿一个参数target, 表示待装饰的对象

/**
* @param {string} metadataKey - 设置或获取时的key
* @param {*} metadataValue - 元数据内容
* @param {Object} target - 待装饰的target
* @param {string} targetKey - target的property
*/
function defineMetadata(metadataKey: any, metadataValue: any, target: Object, targetKey: string | symbol): void;

示例

class DefineMetadata {
    static staticMethod() {}
    static staticProperty = 'static';
    getName() {}
}

const type = 'type';
// 给DefineMetadata设置元数据type, 值为class
Reflect.defineMetadata(type, 'class', DefineMetadata);
// 给DefineMetadata.staticMethod设置元数据type, 值为staticMethod
Reflect.defineMetadata(type, 'staticMethod', DefineMetadata.staticMethod);
// 给DefineMeatadata.prorotype.getName设置元数据type, 值为method
Reflect.defineMetadata(type, 'method', DefineMetadata.prorotype.getName);
// 给DefineMetadata的staticProperty属性设置元数据type, 值为staticProperty
Reflect.defineMetadata(type, 'staticProperty', DefineMetadata, 'staticProperty');

// 获取DefineMetadata身上的type元数据
const t1 = Reflect.getMetadata(type, DefineMetadata); // class
// 获取DefineMetadata.staticMethod身上的type元数据
const t2 = Reflect.getMetadata(type, DefineMetadata.staticMethod); // staticMethod
// 获取DefineMetadata.prototype.getName身上的type元数据
const t3 = Reflect.getMetadata(type, DefineMetadata.prototype.getName); // method
// 获取DefineMetadata上staticProperty属性的type元数据
const t4 = Reflect.getMetadata(type, DefineMetadata, 'staticProperty'); // staticProperty

console.log(t1, t2, t3, t4); // class staticMethod method staticProperty

注意: t4定义和获取不一样的地方, 比如t2到t3都有两种写法, 一种就是将target转换为对应的对象且必须是对象, 以t2为例, 也可以写为

Reflect.defineMetadata(type, 'staticMethods', DefineMetadata, 'staticMethod');
const t2 = Reflect.getMetadata(type, DefineMetadata, 'staticMethod');

注意: 这两种方式不能混合使用, 比如下面这种是不对的:

Reflect.defineMetadata(type, 'staticMethod', DefineMetadata, 'staticMethod');
const t2 = Reflect.getMetadata(type, DefineMetadata.staticMethod);

hasMetadata方法

该方法返回布尔值, 表明该target或其原型链上有没有对应的元数据

/**
* @param {string} metadataKey - 元数据的key
* @param {Obejct} target - 定义的对象
* @param {string} targetKey - 定义对象的属性(重载参数), 可选
* @returns 在target或其原型链上返回true.
*/
function hasMetadata(metadataKey: string, target: Object, targetKey?: symbol | string): boolean;

示例

const type = 'type';
class HasMetadataClass {
    @Reflect.metadata(type, 'staticProperty')
    static staticProperty = '';
}

// 给HasMetadataClass定义一个type元数据, 值为class
Reflect.defineMetadata(type, 'class', HasMetadataClass);
const t1 = Reflect.hasMetadata(type, HasMetadataClass); // true
const t2 = Reflect.hasMetadata(type, HasMetadataClass, 'staticProperty'); // true
console.log(t1, t2); // true true

其余的像实例属性/方法, 静态方法都以此类推

hasOwnMetadata方法

Object.prototype.hasOwnProperty类似, 是只查找对象上的元数据, 而不会继续想上查找原型链上的, 其余的跟hasMetadata一致

const type = 'type';
class Parent {
    @Reflect.metadata(type, 'getName')
    getName() {}
}

@Reflect.metadata(type, 'class')
class HasOwnMetadataClass extends Parent {
    @Reflect.metadata(type, 'static')
    static staticProperty() {}

    @Reflect.metadata(type, 'method')
    method() {}
}

// 判断HasOwnMetadataClass有没有type这个元数据
const t1 = Reflect.hasOwnProperty(type, HasOwnMetadataClass); // true
// 判断HasOwnMetadataClass的staticProperty属性有没有type这个元数据
const t2 = Reflect.hasOwnMetadata(type, HasOwnMetadataClass, 'staticProperty'); // true
// 判断HasOwnMetadataClass.prototype的method属性有没有type这个元数据
const t3 = Reflect.hasOwnMetadata(type, HasOwnMetadataClass.prototype, 'method'); // true
// 判断HasOwnMetadataClass.prototype的getName属性有没有type这个元数据
const t4 = Reflect.hasOwnMetadata(type, HasOwnMetadataClass.prototype, 'getName'); // false
// 判断HasOwnMetadataClass.prototype的getName属性有没有type这个元数据, 这里的结果为true, 因为HasOwnMetadata.prototype上面没有这个属性, 但是HasOwnMetadata的原型链上有getName这个属性
const t5 = Reflect.hasMetadata(type, HasOwnMetadataClass.prototype, 'getName'); // true

console.log(t1, t2, t3, t4, t5); // true true true false true

注意: t4和t5的区别

getMetadata方法

这个属性在之前验证各个属性的时候就已经使用过了, 就是用于获取target的元数据值, 会往原型链上找

/**
* @param {string} metadataKey - 元数据key
* @param {Object} target - 元数据定义的target
* @param {string} targetKey - 可选项, 是否选择target的某个key
* @returns 如果找到了元数据则返回元数据值, 否则返回undefined
**/
function getMetadata(metadataKey: string, target: Object, targetKey?: string | symbol): any;

getOwnMetadata方法

与hasOwnMetadata和hasMetadata的区别一样, 是否往原型链上找

getMetadataKeys方法

类似Object.keys, 返回该target以及原型链上target的所有元数据的keys

const type = 'type';
@Reflect.metadata('parent', 'parent')
class Parent {
    getName() {}
}

@Reflect.metadata(type, 'class')
class HasOwnMetadataClass extends Parent {
    @Reflect.metadata(type, 'static')
    static staticProperty() {}

    @Reflect.metadata('bbb', 'method')
    @Reflect.metadata('aaa', 'method')
    method() {}
}

// 获取HasOwnMetadataClass身上以及原型链上的所有元数据
const t1 = Reflect.getMetadataKeys(HasOwnMetadataClass); // type parent
// 获取HasOwnMetadataClass中method属性身上的以及原型链上的所有元数据
const t2 = Reflect.getMetadataKeys(HasOwnMetadataClass.prototype, 'method'); // aaa bbb

t1很好理解, 因为会向上找原型链的parent, t2好像多了一些东西, desingn: 开头的, 先不管他, 看看aaa 和 bbb 的顺序是和我们添加的顺序是相反的, 还记得之前说过装饰器的顺序是从右到左的, 所以先应用bbb aaa在应用design:

getOwnMetadataKeys方法

跟getMetadataKeys一样, 只是不向原型链中查找

deleteMetadata方法

用于删除元数据

/**
* @param {string} metadataKey - 元数据key
* @param {Object} target - 元数据定义的对象
* @param {string} targetKey - 对象对应的key, 可选参数
* @returns 如果对象上有该元数据, 返回true, 否则返回false
*/
function deleteMetadata(metadataKey: string, target: Object, targetKey?: symbol | string): boolean;

示例

const type = 'type';
@Reflect.metadata(type, 'class')
class DeleteMetadata {
    @Reflect.metadata(type, 'static')
    static staticMethod() {}
}

// 删除DeleteMetadata身上的type元数据
const res1 = Reflect.deleteMetadata(type, DeleteMetadata); // true
// 删除DeleteMetadata上staticMethod属性身上的type元数据
const res2 = Reflect.deleteMetadata(type, DelteMetadata, 'staticMethod'); // true
// 再次删除DelelteMetadata身上的type元数据, 这次返回false, 因为在之前已经删除过了
const res3 = Reflect.deleteMetadata(type, DelteMetadata); // false
console.log(res1, res2, res3); // true true false

design:

好了还有一个问题没有解决, 就是之前说的在getMetadataKey时出现的design:xxx的内容是怎么来的, 表示什么意思呢?design:type 表示被装饰的对象是什么类型, 比如是字符串? 数字? 还是函数等. design:paramtypes 表示被装饰对象的参数类型, 是一个表示类型的数组, 如果不是函数, 则没有该key.design:returntype 表示被装饰对象的返回值属性, 比如字符串, 数字或函数等.

示例

@Reflect.metadata('type', 'class')
class A {
    constructor(public name: string, public age: number) {}

    @Reflect.metadata(undefined, undefined)
    method(): boolean {
        return true;
    }
}

// 获取A的design:paramtypes元数据
const t1 = Reflect.getMetadata('design:paramtypes', A); // [[Function: String], [Function: Number]]
// 获取A.prototype上的method属性的design:returntype元数据
const t2 = Reflect.getMetadata('design:returntype', A.prototype, 'method'); // [Function: Boolean]
// 获取A.prototype上的method属性的design:type元数据
const t3 = Reflect.getMetadata('design:type', A.prototype, 'method'); // [Function: Function]

console.log(t1, t2, t3); // [[Function: String], [Function: Number], [Function: Boolean], [Function: Function]]

注意:

  1. 没有装饰的target是get不到这些metadata的
  2. 必须手动指定类型, 无法进行推断, 比如method方法如果不指定, 返回值为boolean, 那么t2将是undefined
  3. 应用的顺序为: type → paramtypes → returntype