JavaScript中的反射-Reflect
Reflect 是一个内置对象,提供了拦截 JavaScript 操作的方法,同时它不是一个函数对象,所以是不可 new 的,但是它提供了一部分静态方法:
| Reflect.apply(target,thisArgument,argumentsList) | 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似 |
|---|---|
| Reflect.construct(target,argumentsList[,newTarget]) | 对构造函数进行 new 操作,相当于执行 new target(...args) |
| Reflect.defineProperty(target,propertyKey,attributes) | 和 Object.defineProperty() 类似。如果设置成功就会返回 true |
| Reflect.deleteProperty(target,propertyKey) | 作为函数的 delete 操作符,相当于执行 delete target[name] |
| Reflect.get(target,propertyKey[,receiver]) | 获取对象身上某个属性的值,类似于 target[name] |
| Reflect.getOwnPropertyDescriptor(target,propertyKey) | 类似于 Object.getOwnPropertyDescriptor() 。如果对象中存在该属性,则返回对应的属性描述符,否则返回 undefined |
| Reflect.getPrototypeOf(target) | 类似于 Object.getPrototypeOf() |
| Reflect.has(target, propertyKey) | 判断一个对象是否存在某个属性,和 ****in运算符 ****的功能完全相同 |
| Reflect.ownKeys(target) | 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys() , 但不会受enumerable 影响) |
| Reflect.set(target,propertyKey,value[,receiver]) | 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true |
| Reflect.setPrototypeOf(target,prototype) | 设置对象原型的函数。返回一个 Boolean,如果更新成功,则返回 true |
// 定义一个对象
const cat = {
name: "Tom",
age: 3,
eat: () => {
console.log(`${cat.name} 在吃鱼`);
},
};
// 调用对象的方法
Reflect.apply(cat.eat, this.cat, []);
有了反射,意味着在操作某个对象时可以创建代理对象,然后利用反射对对象进行增强操作,比如:
// 定义一个对象
const cat = {
name: "Tom",
age: 3,
eat: () => {
console.log(`${cat.name} 在吃鱼`);
},
};
// 调用对象的方法
Reflect.apply(cat.eat, this.cat, []);
// 创建代理
class Proxy {
constructor(cat) {
this.cat = cat;
}
eatMax(act) {
console.log(`吃鱼前,先${act}`);
Reflect.apply(this.cat.eat, this.cat, []);
console.log(`吃饱了`);
}
}
const proxy = new Proxy(cat);
// 增强了 eat 方法
proxy.eatMax("洗脸");
这样做,就可以将一个原始的对象经过代理增强了本身的方法,利用这一点就可以实现 IoC
IOC(控制反转)
- IOC(Inversion of Control):控制反转,一种设计思想,就是说,当一个应用程序需要一个对象的时候,不再是由应用程序去创建这个对象,而是交给 IOC 容器来创建和管理,也就是对对象的控制权由应用程序本身转移到了 IOC 容器中,这种方式基本上通过依赖查找的方式来实现
- IOC容器:一个应用程序运行时的容器,它负责对象实例化,配置和组装组件,通过读取配置元数据来获取实例化,配置和组装组件的指令
举个例子:
没有IOC容器的情况下
class A{
constructor(b){
this.b = b
}
}
class B{
constructor(name){
this.name = name;
}
}
// A 依赖于 B,当创建 A 的实例的时候,需要在构造函数中传入一个 B 的实例
const a = new A(new B('Tom'));
console.log(a.b.name); // 输出 Tom
每次创建 A 实例时,依赖的 b 的实例每次都需要手动创建,导致有两个问题:
- 每次都需要在内存中开辟一块地方来保存 b 的实例,及其占用内存
- 每次创建出来的 b 的实例都是不同的,无法达到复用
有IOC容器的情况下:
class A {
constructor(b) {
this.b = b;
}
}
class B {
constructor(name) {
this.name = name;
}
}
// 假设设置一个IoC容器,将 A 和 B 都注入到IoC容器中
class IoC {
constructor() {
const b = new B("Tom");
this.b = b;
this.a = new A(b);
return this;
}
}
// 获取Ioc容器
const Ioc = new IoC();
// 这样,在需要 a 时,直接向Ioc容器中要实例,不需要手动 new A 的实例
const a = Ioc.a;
const b = Ioc.b;
console.log(b.name);
- 可以达到复用效果,不用手动创建,每次只需要向 IOC 容器中获取
DI(依赖注入)
- DI(Dependency Injection):依赖注入,指的是组件之间传递依赖关系的过程中,将依赖关系在容器的内部处理,实现了对象之间的解耦
- IOC 和 DI 描述的其实是同样一件事,只是角度不同
- IOC 站在对象的角度,把实例化,管理的权利交给了 IOC 容器
- DI 站在容器的角度,容器会把一个对象需要依赖其他对象时,把这些其他对象在创建这个对象的时候就注入进去
装饰器
装饰器提供了一种为类声明和成员添加注释和元编程语法的方法
启用装饰器支持:
命令行:tsc --target ES5 --experimentalDecorators
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
应用场景:
- 日志记录:在方法或类上添加日志功能,用于记录方法的执行过程和结果
- 性能监控:在方法或类上添加性能监控功能,用于计算方法的执行时间
- 权限验证:在方法或类上添加权限验证功能,用于检查用户是否有权执行某个操作
- 数据验证:在方法或类上添加数据验证功能,用于检查输入数据是否合法
- 缓存处理:在方法或类上添加缓存处理功能,用于缓存方法的结果
概念
装饰器是一种特殊的声明,可以附加到 类声明、方法、accessor、属性 或 参数。装饰器使用 @expression ****形式,其中 expression 必须评估为一个函数,该函数将在运行时调用,并带有有关装饰声明的信息
function sealed(target) {
// do something with 'target' ...
}
装饰器工厂
自定义如何将装饰器应用于声明,可以编写一个装饰器工厂。装饰器工厂只是一个函数,它返回将由装饰器在运行时调用的表达式
function color(value: string) {
// this is the decorator factory, it sets up
// the returned decorator function
return function (target) {
// this is the decorator
// do something with 'target' and 'value'...
};
}
function decoration(value:number){
// value 是通过装饰器传进来的值
// return 的内容将会替换掉原来的构造方法
return function(target: any){
// target 获取当前的类
// 可以在这对类的原型对象进行操作
console.log(target) // 这里获取到的是 Cat 这个类
target.prototype.age = value;
}
}
@decoration(18)
class Cat {
name: string;
constructor(name: string){
this.name = name;
}
}
let tom: any = new Cat('tom');
console.log(tom.name); // 'tom'
console.log(tom.age); // 18
多个装饰器执行顺序
可以添加多个装饰器,在 TypeScript 中对单个声明评估多个装饰器时执行以下步骤:
- 每个装饰器的表达式都是从上到下计算的
- 然后将结果作为函数从下到上调用
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): called");
};
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
};
}
class ExampleClass {
@first()
@second()
method() {}
}
按照出入栈的执行顺序:
- first(): factory evaluated
- second(): factory evaluated
- second(): called
- first(): called
类装饰器
- 类声明之前开始
- 应用于构造函数
- 用于观察、修改或替换类定义
- 构造函数作为其唯一参数
- 如果类装饰器返回一个值,它将用提供的构造函数替换类声明
- 注意:如果返回新的构造函数,则必须注意维护原始原型。在运行时应用装饰器的逻辑不会执行此操作
function decoration(constructor: Function){
// 参数是构造函数(也可以说是 Cat 这个类)
console.log('===》装饰器执行时机《===');
console.log('===》参数内容《===');
console.log(constructor);
console.log('=================');
console.log('===》为类添加属性《===');
constructor.prototype.eat = () => {console.log('吃鱼');};
constructor.prototype.type = '我是猫';
}
@decoration
class Cat {
name: string;
constructor(name: string){
console.log('===》构造器执行时机《===');
this.name = name;
}
}
let tom: any = new Cat('tom');
console.log(tom.name); // "tom"
// 注意:由于装饰器无法改变TypeScript的类型,所以type和eat()对于TypeScript来说仍然是未知的
console.log(tom.type); // "我是猫"
tom.eat(); // "吃鱼"
function decoration(value:number){
// value 是通过装饰器传进来的值
// return 的内容将会替换掉原来的构造方法
return function(target: any){
// target 获取当前的类
// 可以在这对类的原型对象进行操作
console.log(target) // 这里获取到的是 Cat 这个类
target.prototype.age = value;
}
}
@decoration(18)
class Cat {
name: string;
constructor(name: string){
this.name = name;
}
}
let tom: any = new Cat('tom');
console.log(tom.name); // 'tom'
console.log(tom.age); // 18
方法装饰器
- 在方法声明之前声明
- 应用于方法的属性描述符,可用于观察、修改或替换方法定义
有以下三个参数:
- 静态成员的类的构造函数,或者实例成员的类的原型
- 成员的名称
- 成员的属性描述符
function enumerable(value: boolean) {
console.log('===》装饰器执行时机《===');
return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {
console.log('成员构造方法(如果是实例的话就是实例原型)(当前类)===>',target);
console.log('成员名称(当前方法名称)===>',propertyKey);
console.log('成员属性描述符===>',descriptor); // 包含 writable(可写),enumerable(可列举),configurable(可配置)
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
const greeter = new Greeter("greeter");
const res = greeter.greet();
console.log(res)
访问器装饰器(getter方法)
参数:
- 静态成员的类的构造函数,或者实例成员的类的原型
- 成员的名称
- 成员的属性描述符
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
// ---cut---
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;
}
}
const point = new Point(1,2);
// 此时不能再修改point的x与y
point.x = 1; // 报错
point.y = 2; // 报错
属性装饰器
参数:
- 静态成员的类的构造函数,或者实例成员的类的原型
- 成员的名称
import "reflect-metadata";
const formatMetadataKey = Symbol("format");
// 这个注解在执行的时候,会把接收到的字符串模版放到元数据上
// 这样当实例化的时候实例也可以通过元数据重新拿到这个 formatMetadataKey 字符串模版
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
// 从当前对象的元数据中获取 formatMetadataKey 字符串模版
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
// 这里是从 greeting 上取字符串模版(装饰器初始化的时候将模版设置进了这个属性的元数据上)
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
const greeter = new Greeter('xiaoming');
console.log(greeter.greet()) // "Hello, xiaoming"
参数装饰器
参数:
- 静态成员的类的构造函数,或者实例成员的类的原型
- 成员的名称
- 函数参数列表中参数的序号索引
// target:静态属性指构造函数,实例属性、实例方法值构造函数的原型
// methodName:方法的名称
// paramIndex:参数的索引
function addAge(target: any, methodName: string, paramIndex: number) {
// 构造方法:{ login: [Function (anonymous)] } 方法名:login 当前参数的索引:1
console.log(target, methodName, paramIndex);
target.age = 18; // 设置默认值为18
}
class Person {
age: number|undefined;
login(username: string, @addAge password: string) {
console.log(this.age, username, password); // 18 admin admin123
}
}
let p = new Person();
p.login('admin', 'admin123');
元数据
实验性功能
安装:npm i reflect-metadata --save
命令行设置:tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
启用后,只要导入了 reflect-metadata 库,就会在运行时公开额外的设计时类型信息
// @emitDecoratorMetadata
// @experimentalDecorators
// @strictPropertyInitialization: false
import "reflect-metadata";
class Point {
constructor(public x: number, public y: number) {}
}
class Line {
private _start: Point;
private _end: Point;
@validate
set start(value: Point) {
this._start = value;
}
get start() {
return this._start;
}
@validate
set end(value: Point) {
this._end = value;
}
get end() {
return this._end;
}
}
function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) {
let set = descriptor.set!;
descriptor.set = function (value: T) {
let type = Reflect.getMetadata("design:type", target, propertyKey);
if (!(value instanceof type)) {
throw new TypeError(`Invalid type, got ${typeof value} not ${type.name}.`);
}
set.call(this, value);
};
}
const line = new Line()
line.start = new Point(0, 0)
// @ts-ignore
// line.end = {}
// Fails at runtime with:
// > Invalid type, got object not Point