文章目录
- 装饰器
- 一、类装饰器
- 1. 基本用法
- 2. 装饰器返回值
- 3. 构造类型
- 4. 替换被装饰的类
- 二、装饰器工厂
- 三、装饰器组合
- 四、属性装饰器
- 五、方法装饰器
- 六、访问器装饰器
- 七、参数装饰器
装饰器
装饰器本质是一种特殊函数,可以对类、属性、方法、参数进行扩展,使代码更简洁。
一、类装饰器
1. 基本用法
Demo函数会在Person类定义时即刻执行;target参数是被装饰的类,即Person
typescript">function Demo(target:Function){console.log(target)
}
@Demo //这句话相等于 Demo(Person)
class Person {constructor(public name:string,public age:number){ }
}
-
应用举例(追加方法)
typescript">function CustomString(target:Function){target.prototype.toString = function(){ //在原型中添加方法return JSON.stringify(this) //作用对象是当前实例,返回JSON格式的数据} } @CustomString class Person{constructor(public name:string,public age:number){ }speak(){console.log(`我叫${this.name}`)} } const p1 = new Person("张三",18) //在 JavaScript 中,几乎所有的对象都有一个 toString() 方法。它通常用于返回对象的字符串表示。 console.log(p1.toString()) //没有装饰器时输出:[object Object] //添加装饰器后输出:{"name":"张三","age":18}
-
封闭原型对象,禁止随意操作原型对象:
Object.seal(target.prototype)
2. 装饰器返回值
- 若有返回值:返回一个新的类,这个新类将会替换掉被装饰的类
- 若无返回值:undefined,不会被替换
3. 构造类型
构造类型用于描述构造函数的类型,它定义了如何通过
new
关键字创建类的实例。
通俗一点:可以用new
关键字调用的类
typescript">/* new :可以被new...arg 可以接受任意数量的参数any 可以接受任意类型的参数=> { }:返回类型时空对象(非null,undefined) */
type Constructor = new (...args:any[]) =>{ }
//现在的需求是,传入的fn得是一个类
function test( fn:Constructor){ }
class p1{ }
const p2 = () => { }
test(p1)
test(p2) //报错
4. 替换被装饰的类
对于高级的装饰器,不仅是覆盖一个原型上的方法,还要用更多功能。
-
现有需求:设计一个
setime
装饰器,可以给实例添加一个属性,用于记录实例对象创建的时间,再添加方法读取时间。typescript">type Constructor = new (...args: any[]) => {} //构造类型 //接口(自动合并的特性) interface Person {printDate():void //让Person知道自己多了一个函数 } function SetTime<T extends Constructor>(target: T) { //装饰的必须是可以new的类return class extends target{ //继承原类,名字不变createTime: Dateconstructor(...args: any[]) { //目的是提高兼容性super(...args)this.createTime = new Date()}printDate(){console.log(`该对象的创建时间是:${this.createTime}`)}} } @SetTime class Person {constructor(public name: string, public age: number) { } } const p1 = new Person("李华",18) p1.printDate() //输出:该对象的创建时间是:${this.createTime}
二、装饰器工厂
-
装饰器工厂不是装饰器,装饰器工厂的返回值是装饰器,装饰器工厂的作用主要是它接收的参数可以供给装饰器使用
-
现有需求,在原有类上添加一个方法,这个方法能打印name指定次数
typescript">interface User{introduce():void } function LogName(n:number){return function(target:Function) {target.prototype.introduce = function(){ //在原型上添加方法for(let i = 0 ;i<n;i++){console.log(`我叫${this.name}`) //打印实例姓名}}} } @LogName(3) //相当于LogName(3)(User) 因为LogName返回值是一个函数(装饰器),所以还可以接收参数 class User{constructor(public name:string){ } } const p1 = new User("李华") p1.introduce()
三、装饰器组合
-
装饰器组合使用的顺序:
先【从上到下】执行所有的装饰器工厂,再【从下到上】执行所有装饰器(包括装饰器工厂里的装饰器)
四、属性装饰器
-
属性装饰器:对属性的行为进行装饰
-
接收的参数有两个:
- 第一个:如果装饰的属性是静态属性则参数为类,否则为类的原型对象
- 第二个:修饰的属性名
补充:什么是static属性?
答:静态属性是类本身的属性,而不是类的实例的属性。通过类名可以访问静态属性,而无法通过实例来访问 -
现在有需求,需要在类属性的值修改之际,就打印出修改的信息。这时候就需要用到属性装饰器
typescript">function State(target: object, propertykey: string) {//私有属性,通过p1.age访问的值实际是p1.__age(这是个私有属性,只允许当前用户修改)let key = `__${propertykey}`Object.defineProperty(target, propertykey, { //添加原型属性get() {return this[key]},set(newValue) {console.log(`${propertykey}的最新值是${newValue}`)this[key] = newValue}enumerable:true, //可枚举性configurable:true //可修改性}) } class Person {@State age: numberconstructor(name: number) {this.age = name //当访问this.age时,先在原型链上找是否有这个属性,再做修改} }
-
测试用例
typescript">const p1 = new Person(18) const p2 = new Person(20) p1.age = 20 //输出:age的最新值是20 p2.age = 30 //输出:age的最新值是30 console.log(p1.age) //输出:20 console.log(p2.age) //输出:30
五、方法装饰器
-
接收的参数有三个:
- 第一个:如果装饰的方法是静态方法则参数为类,否则为类的原型对象
- 第二个:装饰的方法名
- 第三个:方法的描述对象,其中的value值是被装饰的方法
-
应用举例:在函数执行时,打印函数执行日志
typescript">function Logger(target: object, propertykey: string, descriptor: PropertyDescriptor) {//存储原始方法const origin = descriptor.value//替换原始方法descriptor.value = function (...args: any[]) {console.log("函数开始执行...")const result = origin.call(this, ...args) //绑定this指向实例对象,预防this可能发生上下文丢失console.log("函数执行结束...")return result //以免调用的参数是有返回值得,增加兼容性} }class info {constructor(public name: string) { }@Logger speak() { console.log(`我是${this.name}函数`) } }
-
测试用例
typescript">class info {constructor(public name: string) { }@Logger speak() { console.log(`我是${this.name}函数`) } } const info1 = new info("模幂运算") info1.speak() /*输出:函数开始执行...我是模幂运算函数函数执行结束... */
-
注意点:
.call()
和.apply()
方法的区别- 共同点
- 都允许改变函数执行时的
this
,即控制上下文。 - 都能用来调用其他对象的方法,并将该方法的
this
设置为指定的对象。(实际和第一条差不多)
- 都允许改变函数执行时的
- 不同点
call
需要逐个列出参数。apply
需要把参数作为数组传递。
- 共同点
六、访问器装饰器
访问器装饰器的工作机制与普通方法装饰器相似,但它们专门作用于 getter 和 setter。
-
应用案例:定义一个简单的装饰器,在访问某个属性时打印日志
typescript">function log(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {const originalGet = descriptor.get;const originalSet = descriptor.set;// 修改 getterdescriptor.get = function() {console.log(`Getting value of ${String(key)}`);return originalGet?.call(this);};// 修改 setterdescriptor.set = function(value: any) {console.log(`Setting value of ${String(key)} to ${value}`);if (originalSet) {originalSet.call(this, value);}};return descriptor; } class Person {private _name: string = '';@logget name() {return this._name;}@logset name(value: string) {this._name = value;} } const p = new Person(); p.name = 'Alice'; // 输出: Setting value of name to Alice console.log(p.name); // 输出: Getting value of name, 然后输出: Alice
七、参数装饰器
参数装饰器(Parameter Decorators)是用来装饰类方法中的参数的装饰器。参数装饰器允许我们为方法参数添加元数据,或者在参数被传递给方法之前执行一些额外的操作。