Skip to content

装饰器

装饰器(Decorators)为我们在类的声明及成员上通过编程语法扩展其功能,装饰器以函数的形式声明。

装饰器类型

可用装饰器包括以下几种

装饰器说明
ClassDecorator类装饰器
MethodDecorator方法装饰器
PropertyDecorator属性装饰器
ParameterDecorator参数装饰器

实验性

Decorators 是实验性的功能,所以开发时会提示错误,我们需要启动 Decorator 这个实验性的功能。

首先创建配置文件 tsconfig.js

tsc --init

然后开启以下配置项,来启动装饰器这个实验性的功能。

"experimentalDecorators": true,
"emitDecoratorMetadata": true

然后执行命令,错误就消失了,如果没有 ts-node (opens new window)请先安装

tsc -w

类装饰器

类装饰器是对类的功能进行扩展

  • 首先执行 RoleDecorator 装饰器,然后执行类的构造函数
  • 装饰器会优先执行,这与装饰器与类的顺序无关

装饰器参数

首先介绍装饰器函数参数说明

参数说明
参数一构造函数
  • 普通方法是构造函数的原型对象 Prototype
  • 静态方法是构造函数
typescript
const MoveDecorator: ClassDecorator = (constructor: Function): void => {
    console.log(`装饰器 RoleDecorator `);
}

@MoveDecorator
class Tank {
    constructor() {
        console.log('tank 构造函数');
    }
}

即使把装饰器定义放在类的后面也是先执行装饰器

typescript
@MoveDecorator
class Tank {
    constructor() {
        console.log('tank 构造函数');
    }
}

function MoveDecorator(constructor: Function): void {
    console.log(`装饰器 RoleDecorator `);
}

原型对象

因为可以装饰器上得到构造函数,所以可以通过原型对象来添加方法或属性,供实例对象使用

typescript
const MoveDecorator: ClassDecorator = (constructor: Function) => {
    constructor.prototype.hd = '后盾人'
    constructor.prototype.getPosition = (): { x: number, y: number } => {
        return { x: 100, y: 100 }
    }
}

@MoveDecorator
class Tank {
    constructor() {
        console.log('tank 构造函数');
    }
}
const tank = new Tank()
console.log(tank.getPosition());

不过在编译阶段会提示错误,但这不影响编译生成 js 文件

我们可以通过为类添加默认属性来解决这个错误

typescript
class Tank {
    public hd: string | undefined
    public getPosition() { }
    constructor() {
        console.log('tank 构造函数');
    }
}

或者在调用时使用断言处理

typescript
const tank = new Tank()
console.log((tank as any).getPosition());
//或使用以下方式断言
console.log((<any>tank).getPosition());

语法糖

不需要把装饰器想的很复杂,下面是同样实现了装饰器的功能。只不过是我们人为调用函数,所以可以把装饰器理解为这种调用的语法糖,这样理解就简单些。

typescript
//装饰器是一个函数,接受一个类的构造函数作为参数。通过构造函数查找原型对象,并在原型对象上添加其他功能函数的定义
const MoveDecorator: ClassDecorator = (constructor: Function) => {
    constructor.prototype.hd = '后盾人'
    constructor.prototype.getPosition = (): { x: number, y: number } => {
        return { x: 100, y: 100 }
    }
}

class Tank {
    constructor() {
        console.log('tank 构造函数');
    }
}

MoveDecorator(Tank);
const tank = new Tank()
console.log(tank.getPosition());

装饰器叠加

装饰器可以叠加使用,下面是定义了位置管理与音乐播放装饰器

typescript
//位置控制
const MoveDecorator: ClassDecorator = (constructor: Function): void => {
    constructor.prototype.hd = '后盾人'
	console.log('MoveDecorator');
    constructor.prototype.getPosition = (): void => {
        console.log('获取坐标');
    }
}

//音乐播放
const MusicDecorator: ClassDecorator = (constructor: Function): void => {
	console.log('MusicDecorator');
    constructor.prototype.playMusic = (): void => {
        console.log('播放音乐');
    }
}

@MoveDecorator
@MusicDecorator
class Tank {
    constructor() {
    }
}
const tank = new Tank();
(<any>tank).playMusic();
(<any>tank).getPosition();

多类复用

定义好装饰器后,可以为多个类复用,比如下面的玩家与坦克

typescript
//位置控制
const MoveDecorator: ClassDecorator = (constructor: Function): void => {
    constructor.prototype.hd = '后盾人'
    constructor.prototype.getPosition = (): void => {
        console.log('获取坐标');
    }
}
//音乐播放
const MusicDecorator: ClassDecorator = (constructor: Function): void => {
    constructor.prototype.playMusic = (): void => {
        console.log('播放音乐');
    }
}

@MoveDecorator
@MusicDecorator
class Tank {
    constructor() {
    }
}
const tank = new Tank();
(<any>tank).playMusic();
(<any>tank).getPosition();

@MoveDecorator
class Player {
}

const xj: Player = new Player();
(xj as any).getPosition()

响应消息

下面是将网站中的响应消息工作,使用装饰器进行复用。

typescript
//消息响应
const MessageDecorator: ClassDecorator = (constructor: Function): void => {
    constructor.prototype.message = (message: string): void => {
        document.body.insertAdjacentHTML('afterbegin', `<h2>${message}</h2>`)
    }

}

@MessageDecorator
class LoginController {
    login() {
        console.log('登录逻辑');
        this.message('登录成功')
    }
}
const controller = new LoginController();

controller.login()

装饰器工厂

有时有需要根据条件返回不同的装饰器,这时可以使用装饰器工厂来解决。可以在类、属性、参数等装饰器中使用装饰器工厂。

下例根据 MusicDecorator 工厂函数传递的不同参数,返回不同装饰器函数。

typescript
const MusicDecorator = (type: string): ClassDecorator => {
    switch (type) {
        case 'player':
            return (constructor: Function) => {
                constructor.prototype.playMusic = (): void => {
                    console.log(`播放【海阔天空】音乐`);
                }
            }
            break;
        default:
            return (constructor: Function) => {
                constructor.prototype.playMusic = (): void => {
                    console.log(`播放【喜洋洋】音乐`);
                }
            }

    }
}

@MusicDecorator('tank')
class Tank {
    constructor() {
    }
}
const tank = new Tank();
(<any>tank).playMusic();

@MusicDecorator('player')
class Player {
}

const xj: Player = new Player();
(xj as any).playMusic()

方法装饰器

装饰器也可以修改方法,首先介绍装饰器函数参数说明

参数说明
参数一普通方法是构造函数的原型对象 Prototype,静态方法是构造函数
参数二方法名称
参数三属性描述,如果对这个知识点不清楚,请访问后盾人 (opens new window)看向军大叔录制的 js 课程

基本使用

下面使用 ShowDecorator 装饰来修改 show 方法的实现

typescript
const ShowDecorator: MethodDecorator = (
  target: Object,
  propertyKey: string | Symbol,
  descriptor: PropertyDescriptor,
): void => {
  //对象
  console.dir(target)
  //方法名
  console.dir(propertyKey)
  //方法实现
  console.dir(descriptor)
  descriptor.value = () => {
    console.log('houdunren.com')
  }
}

class Hd {
  @ShowDecorator
  show() {
    console.log('show method')
  }
}

const instance = new Hd()
instance.show()

下面是修改方法的属性描述 writable 为 false,这时将不允许修改方法。

typescript
const ShowDecorator: MethodDecorator = (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor): void => {
    descriptor.writable = false
}

class Hd {
    @ShowDecorator
    show() {
        console.log(33);
    }
}

const instance = new Hd;
instance.show()

//装饰器修改了 writable 描述,所以不能重写函数
instance.show = () => { }

静态方法

静态方法使用装饰器与原型方法相似,在处理静态方法时装饰器的第一个参数是构造函数。

typescript
const ShowDecorator: MethodDecorator = (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor): void => {
    descriptor.value = () => {
        console.log('houdunren.com');
    }
}

class Hd {
    @ShowDecorator
    static show() {
        console.log('show method');
    }
}

Hd.show()

属性装饰器

首先介绍装饰器函数参数说明

参数说明
参数一普通方法是构造函数的原型对象 Prototype,静态方法是构造函数
参数二属性名称

基本使用

下面是属性装饰器的定义方式

typescript
const PropDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol): void => {
    console.log(target, propertyKey);
}

class Hd {
    @PropDecorator
    public name: string | undefined = '后盾人'
    show() {
        console.log(33);
    }
}

访问器

下面是定义将属性 name 的值转为小写的装饰器

typescript
const PropDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol): void => {
    let value: string;
    const getter = () => {
        return value
    }
    const setter = (v: string) => {
        value = v.toLowerCase()
    }

    Object.defineProperty(target, propertyKey, {
        set: setter,
        get: getter
    })
}

class Hd {
    @PropDecorator
    public name: string | undefined
    show() {
        console.log(33);
    }
}

const instance = new Hd;
instance.name = 'HouDunRen'
console.log(instance.name);

参数装饰器

可以对方法的参数设置装饰器,参数装饰器的返回值被忽略。

装饰器函数参数说明

参数说明
参数一普通方法是构造函数的原型对象 Prototype,静态方法是构造函数
参数二参数所在的方法名称(参数是那个函数的参数)
参数三参数所在索引位置

基本使用

typescript
const ParameterDecorator: ParameterDecorator = (target: any, propertyKey: string | Symbol, parameterIndex: number): void => {
    console.log(target, propertyKey, parameterIndex);
}

class Hd {
    show(name: string, @ParameterDecorator url: string) {
    }
}