从头开始学习nestjs-第一章-前置知识

184 阅读1分钟

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 中对单个声明评估多个装饰器时执行以下步骤:

  1. 每个装饰器的表达式都是从上到下计算的
  2. 然后将结果作为函数从下到上调用
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

方法装饰器

  • 在方法声明之前声明
  • 应用于方法的属性描述符,可用于观察、修改或替换方法定义

有以下三个参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  2. 成员的名称
  3. 成员的属性描述符
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方法)

参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  2. 成员的名称
  3. 成员的属性描述符
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; // 报错

属性装饰器

参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  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"

参数装饰器

参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  2. 成员的名称
  3. 函数参数列表中参数的序号索引
// 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