装饰器
装饰器提供了一种为类声明和成员添加注释和元编程语法的方法
启用装饰器支持:
命令行: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