日期:2022年7月23日标签:JavaScript

JavaScript Reflect 和 reflect-metadata #

阅读本节内容之前,请先阅读 decorator 有关内容:

Global Reflect #

ECMAScript 2015 增加了 Reflect 对象。Reflect 提供了一些非常有用的功能。

Reflect.ownKeys:

// This object will have prop = "cool"
class RootObject {
    public prop: string = "cool";
}

// Its prototype will have foo = "bar"
RootObject.prototype = { foo: "bar" } as any;

// Create an instance
const root = new RootObject();

// for...in moves up the prototype chain
// tslint:disable-next-line:forin
for (const key in root) {
    console.log(key);
}
// prop
// foo

// hasOwnProperty will prevent this
// but requires an extra conditional
for (const key in root) {
    if (root.hasOwnProperty(key)) {
        console.log(key);
    }
}
// prop

// Reflect.ownKeys solves it in one line
for (const key of Reflect.ownKeys(root)) {
    console.log(key);
}
// prop

Reflect.has

// The visibility compiles out but whatever
class Demo {
    public foo: number = 1;
    protected bar: number = 2;
    private baz: number = 3;
}

// Create an instance
const demo = new Demo();

console.log(Reflect.has(demo, "foo"));
// true
console.log(Reflect.has(demo, "bar"));
// true
console.log(Reflect.has(demo, "baz"));
// true
console.log(Reflect.has(demo, "qqq"));
// false

Reflect.deleteProperty:

(() => {
    "use strict";
    const sampleDeleteObject = {
        one: 1,
        three: 3,
        two: 2,
    };

    // Delete a property with delete
    console.log(delete sampleDeleteObject.one);
    // true
    // Delete a property with Reflect
    console.log(Reflect.deleteProperty(sampleDeleteObject, "two"));
    // true
    console.log(sampleDeleteObject);
    // { three: 3 }
    // Accidentally try to delete an object
    try {
        // tslint:disable-next-line:no-eval
        console.log(eval("delete sampleDeleteObject"));
    } catch (error) {
        // do nothing
    }
    // Accidentally try to delete an object
    console.log(Reflect.deleteProperty(sampleDeleteObject));
    // true
    console.log(sampleDeleteObject);
    // { three: 3 }
})();

emitDecoratorMetadata #

typescript 提供了一些实验中的 reflection 功能。使用这些功能,必须开启相关设置:

{
    "compilerOptions": {
        "target": "ESNext",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    },
    "include": ["./**/*.ts"]
}

编写如下 ts 文件,并编译:

function LogMethod(
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor,
) {
    console.log(target);
    console.log(propertyKey);
    console.log(descriptor);
}

class Demo {
    @LogMethod
    public foo(bar: number) {
        // do nothing
    }
}
const demo = new Demo();

编译结果:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
function LogMethod(target, propertyKey, descriptor) {
    console.log(target);
    console.log(propertyKey);
    console.log(descriptor);
}
class Demo {
    foo(bar) {
        // do nothing
    }
}
__decorate([
    LogMethod,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", void 0)
], Demo.prototype, "foo", null);
const demo = new Demo();

开启了 emitDecoratorMetadata 后,编译的文件中多了 __metadata 函数。

var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};

__metadata 函数接收 kv(key-value) 键值对给 Reflect.metadata 函数,Reflect.metadata 会存储这些信息,并留作后面使用。默认情况下,emitDecoratorMetadata 会暴露三个属性:

  • design:type: 装饰目标的类型,这里是 Function
  • design:paramtypes:装饰目标的参数类型(数组列表形式),这里是 [Number]
  • design:returnType:装饰目标的返回类型,这里是 void 0

看到这里可能会有个疑惑,那就是 Reflect.metadata 是个啥?

reflect-metadata #

reflect-metadata 是一个 npm package,官方文档也推荐了该 package,该包扩展了 relfection 的能力,安装了 reflect-metadata 后, Reflect.metadata 就定义了。

下面展示了如何通过 reflect-metadata 存储和获取信息:

import "reflect-metadata";

class BasicUsage {
    constructor() {
        // Explicitly define some metadata
        // key, value, target, propertyKey
        Reflect.defineMetadata("foo1", "bar1", this, "baz");
    }

    // Define metadata via a decorator
    // key, value
    @Reflect.metadata("foo2", "bar2")
    public baz() {
        // do nothing
    }
}

const demoBasicUsage = new BasicUsage();

// key, target, propertyKey
console.log(Reflect.getMetadata("foo1", basicUsageDemo, "baz"));
// bar1
console.log(Reflect.getMetadata("foo2", basicUsageDemo, "baz"));
// bar2

在 decorator 中获取 metadata:

import "reflect-metadata";

function LogMethod(
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor,
) {
    // Checks the type of the decorated object
    console.log(Reflect.getMetadata("design:type", target, propertyKey));
    // [Function: Function]
    // Checks the types of all params
    console.log(Reflect.getMetadata("design:paramtypes", target, propertyKey));
    // [[Function: Number]]
    // Checks the return type
    console.log(Reflect.getMetadata("design:returntype", target, propertyKey));
    // undefined
}

class Demo {
    @LogMethod
    public foo(bar: number) {
        // do nothing
    }
}
const demo = new Demo();

(完)

目录