一、简介
- 装饰器本质是一种特殊的
函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁 - 装饰器自2015年在ECMAScript-6中被提出到现在,已接近10年
- 截止目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持。
- 装饰器有5种
- 类装饰器
- 属性装饰器
- 方法装饰器
- 访问器装饰器
- 参数装饰器
备注:虽然TypeScript5.0中可以直接使用类装饰器,但为了确保其他装饰器可用,现阶段使用仍建议使用experimentalDecorators 配置开启装饰器支持,而且不排除在未来的版本中,官方会进一步调整装饰器的相关语法!
二、类装饰器
1. 基本语法
类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑。
/*
Demo函数会在Person类定义时执行
参数说明:
target:参数是被装饰的类,即:Person
*/
function Demo(target:Function){
console.log(target)
}
// 使用装饰器
@Demo
class Person {}
2. 应用举例
需求:定义一个装饰器,实现Person实例调用toString时返回JSON.stringify的执行结果。
//使用装饰器重写toString写法 + 封闭其原型对象
function CustomString(target:Function){
// 向被装饰类的原型上添加自定义的toString方法
target.prototype.toString = function () {
return JSON.stringify(this)
}
//封闭其原型对象,禁止随意操作其原型对象
Object.seal(target.prototype)
}
// 使用 CustomString装饰器
@CustomString
class Person {
constructor(public name:string,public age:number){}
speak() {
console.log('你好呀!')
}
}
// 测试代码如下
let p1 = new Person('张三',18)
// 输出: {'name':'张三',‘age’:18}
console.log(p1.toString())
3. 关于返回值
类装饰器有返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类 类装器无返回值:若类装饰器无返回值或返回undefined,那被装饰的类不会被替换。
function demo(target:Function) {
// 装饰器有返回值时,该返回值会替换掉被装饰的类
return class {
test(){
console.log(200)
console.log(300)
console.log(400)
}
}
}
@demo
class Person {
test(){
console.log(100)
}
}
console.log(Person)
4. 关于构造类型
在TypeScript中,Function 类型所表示的范围十分广泛,包括:普通函数、箭头函数、方法等等。但并非Function 类型的函数都可以被new关键字实例化。例如箭头函数是不能被实例化的,那么TypeScript中如何声明一个构造函数呢?有以下两种方式:
仅声明构造类型
/*
。new 表示:该类型是可以用new操作符调用
。 ...args 表示:构造器可以接受【任意数量】的参数
。 any[] 表示:构造器可以接受【任意类型】的参数
。 {} 表示:返回类型是对象(非null、非undefined的对象)
*/
//定义Constructor 类型,其含义是构造类型
type Constructor = new (...args:any[]) => {}
function test(fn:Constructor){}
class Person{}
test(Person)
声明构造类型 + 指定静态属性
//定义Constructor 类型,且包含一个静态属性wife
type Constructor = {
new (...args:any[]):{} // 构造签名
wife:string;// wife属性
}
function test(fn:Constructor){}
class Person{
static wife = 'asd'
}
test(Person)
5. 替换被装饰的类
对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例如添加新的方法和状态。
设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取创建时间。
interface Person {
getTime():void
}
//自定义类型Class
type Constructor = new (...args:any[]) => {}
//创建一个装饰器,为类添加日志功能和创建时间
function LogTime<T extends Constructor>(target:T){
return class extends target {
createdTime:Date;
constructor(...args:any[]){
super(...args);
this.createTime = new Date(); //记录对象创建时间
}
getTime(){
return '该对象创建时间为:${this.createTime}'
}
}
}
@LogTime
class Person {
name: string
age: number
constructor(name:string,age:number){
this.name = name
this.age = age
}
speak(){
console.log('你好呀!')
}
}
const p1 = new Person('张三',18)
cosole.log(p1.getTime())
三、装饰器工厂
装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制控制器的行为
需求:定义一个LogInfo类装饰器工厂,实现Person实例可以调用到introduce方法,且introduce中输出内容的次数,由LogInfo接收的参数决定
interface Person {
introduce: () => void
}
// 定义一个装饰器工厂LogInfo,它接受一个参数n,返回一个类装饰器
function LogInfo(n:number){
// 装饰器函数,target是被装饰的类
return function(target:Function){
target.prototype.introduce = function() {
for (let i = 0;i < n ;i++){
console.log(`我的名字:${this.name},我的年龄:${this.age}`)
}
}
}
}
@LogInfo(5)
class Person {
constructor(
public name:string,
public age:number
){}
speak() {
cosole.log('你好呀!')
}
}
let p1 = new Person('张三',18)
//console.log(p1) // 打印p1是:_classThis,转换的JS版本比较旧时,会出现,不必纠结
p1.speak()
四、装饰器组合
装饰器可以组合使用,执行顺序为:先 【由上到下】执行所有的装饰器工厂,一次获取到装饰器,然后 再【由下到上】执行所有的装饰器。
装饰器组合 - 执行顺序
//装饰器
function test1(target:Function){
console.log('tes1')
}
//装饰器工厂
function test2(){
console.log('tes2工厂')
return function (target:Function){
console.log('tes2')
}
}
//装饰器工厂
function test3(){
console.log('test3工厂')
return function (target:Function){
console.log('test3')
}
}
//装饰器
function test4(target:Function){
console.log('tes4')
}
@test1
@test2()
@test3()
@test4()
class Person{}
装饰器组合 - 应用
// 自定义类型Class
type Constructor = new (...args:any[]) = {}
interface Person {
introduce():void
getTime():void
}
// 使用装饰器重写toString方法 + 封闭其原型对象
function customToString(target:Function) {
// 向被装饰类的原型上添加自定义的toString方法
target.prototype.toString = function(){
return JSON.stringify(this)
}
//封闭其原型对象,禁止随意操作其原型对象
Object.seal(target.prototype)
}
// 创建一个装饰器,为类添加日志功能和创建时间
function LogTime<T extends Constructor>(target:T){
return class extends target {
createdTime:Date;
constructor(...args:any[]){
super(...args);
this.createTime = new Date(); //记录对象创建时间
}
getTime(){
return '该对象创建时间为:${this.createTime}'
}
}
}
// 定义一个装饰器工厂LogInfo,它接受一个参数n,返回一个类装饰器
function LogInfo(n:number){
// 装饰器函数,target是被装饰的类
return function(target:Function){
target.prototype.introduce = function() {
for (let i = 0;i < n ;i++){
console.log(`我的名字:${this.name},我的年龄:${this.age}`)
}
}
}
}
@customToString
@LogInfo(3)
@LogTime
class Person {
constructor(
public name:string,
public age:number
){}
speak() {
cosole.log('你好呀!')
}
}
const p1 = new Person('张三',18)
console.log(p1.toString())
p1.introduce()
console.log(p1.getTime())
五、属性装饰器
1. 基本语法
/*
参数说明:
。 target:对于静态属性来说值是类(这里对应着Person类),对于实例属性来说值是类的原型对象。
。 propertyKey:属性名
*/
function Demo(target:object,propertyKey:string){
console.log(target,propertyKey)
}
class Person {
@Demo name:string
@Demo age:number
@Demo static school:string
constuctor(name:string,age:number){
this.name = name
this.age = age
}
}
2. 关于属性遮蔽
class Person {
name:string
age:number
constuctor(name:string,age:number){
this.name = name
this.age = age
}
}
let value = 99
// 使用defineProperty给Person原型添加age属性,并配置对应的get与set
Object.defineProperty(Person.prototype,'age',{
get(){
return value
}
set(val){
value = val
}
})
const p1 = new Person('张三',18)
console.log(p1.age) // 18
console.log(Person.prototype.age) // 18
3. 应用举例
定义一个State属性装饰器,来监视属性和修改
function State(target:object,propertyKey:string){
// 存储属性的内部值
let key = `__${propertyKey}`;
// 使用Object.defineproperty 替换类的原始属性
// 重新定义属性,使其使用自定义的getter 和 setter
Object.defineProperty(target,propertyKey,{
get(){
return this[key]
},
set(newVal:string){
console.log(`${propertyKey}的最新值为:${newVal}`)
this[key] = newVal
}
enumerable:true,
configurable:true,
})
}
class Person {
name: string
@State age:number
constructor(name:string,age:number){
this.name = name
this.age = age
}
}
const p1 = new Person('张三',18)
六、方法装饰器
1. 基本语法
/*
参数说明:
target:对于静态方法来说值是类,对于实例方法来说值是原型对象
propertyKey:方法的名称
descriptor:方法的描述对象,其中value属性是被装饰的方法
*/
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class Person {
constructor(
public name:string,
public age:number
){}
// Demo装饰实例方法
@Demo speak(){
console.log(`我的名字:${this.name},我的年龄:${this.age}`)
}
// Demo装饰静态方法
@Demo static isAdult(age:number){
return age >= 18
}
}
const p1 = new Person('张三',18)
p1.speak()
2. 应用举例
- 定义一个Logger方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑
- 定义一个Validate方法装饰器,用于验证数据。
function Logger(target:object,propertyKey:string,descriptor:PropertyDescriptor){
// 保存原始方法
const original = descriptor.value;
// 替换原始方法
descriptor.value = function(...args:any[]){
console.log(`${propertyKey}开始执行。。。。。。`)
const result = original.call(this,...args)
console.log(`${propertyKey}开始完毕。。。。。。`)
return result
}
}
function Validate(maxValue:number){
return function (target:object,propertyKey:string,descriptor:PropertyDescriptor){
// 保存原始方法
const original = descriptor.value;
// 替换原始方法
descriptor.value = function(...args:any[]){
//自定义的验证逻辑
if (args[0] > maxValue){
throw new Error('年龄非法!')
}
// 如果所有参数都符合要求,则调用原始方法
return original.apply(this,args)
}
}
}
class Person {
constructor(
public name:string,
public age:number
){}
@Logger speak(){
console.log(`你好,我的名字:${this.name},我的年龄:${this.age}`)
}
@Validate(120)
static isAult(age:number){
return age >= 18
}
}
const p1 = new Person('张三',18)
p1.speak()
console.log(Person.isAdult(100))
七、访问器装饰器
1. 基本语法
/*
参数说明
target:
对于实例访问器来说值是【所属类的原始对象】
对于静态访问器来说值是【所属类】
propertyKey:访问器的名称,
descriptor:描述对象
*/
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class Person {
@Demo
get address(){
return '深圳南山科技园'
}
@Demo
static get country(){
return '中国'
}
}
2. 应用举例
对Weather类的temp属性的set访问器进行限制,设置的最低温度 -50,最高温度 50
function RangeValidate(min:number,max:number){
return function(target:object,propertyKey:string,descriptor:PropertyDescriptor){
// 保存原始方法
const originalSetter = descriptor.value;
//重写setter 方法,加入范围验证逻辑
descriptor.set = function(value:number){
// 检查设置的值是否在指定的最小值和最大值之间
if(value < min || value > max){
// 如果值不在范围内,抛出错误
throw new Error(`${propertyKey}的值应该在${min}到 ${max}之间!`)
}
// 如果值在范围内,且原始 setter 方法存在,则调用原始 setter 方法
if (originalSetter) {
originalSetter.call(this,value)
}
}
}
}
class Weather {
private _temp:number;
constructor(_temp:number){
this._temp = _temp
}
//设置 温度范围在 -50到 50之间
@RangeValidate(-50,50)
set temp(value){
this._temp = _temp
}
get temp(){
return this._temp
}
}
const w1 = new Weather(25)
console.log(w1)
w1.temp = 67
console.log(w1)
八、参数装饰器
/*
参数说明
target:
如果修饰的是【实例方法】的参数,target是类的【原型对象】
如果修饰的是【静态方法】的参数,target是【类】
propertyKey:参数所在的方法的名称
parameterIndex:参数在函数参数列表中你的索引,从0开始。
*/
function Demo(target:object,propertyKey:string,parameterIndex:number){
console.log(target)
console.log(propertyKey)
console.log(parameterIndex)
}
// 类定义
class Peron {
constructor(public name:string)
speak(@Demo message1:any,message2:any){
console.log(`${this.name}想对说:${this.message1},${this.message2}`)
}
}