【TypeScript】扩展:装饰器

server/2025/2/3 9:45:30/

文章目录

    • 装饰器
      • 一、类装饰器
          • 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()
    

三、装饰器组合

  • 装饰器组合使用的顺序:

    先【从上到下】执行所有的装饰器工厂,再【从下到上】执行所有装饰器(包括装饰器工厂里的装饰器)

四、属性装饰器

  • 属性装饰器:对属性的行为进行装饰

  • 接收的参数有两个:

    1. 第一个:如果装饰的属性是静态属性则参数为类,否则为类的原型对象
    2. 第二个:修饰的属性名

    补充:什么是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
    

五、方法装饰器

  • 接收的参数有三个:

    1. 第一个:如果装饰的方法是静态方法则参数为类,否则为类的原型对象
    2. 第二个:装饰的方法名
    3. 第三个:方法的描述对象,其中的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()方法的区别

    • 共同点
      1. 都允许改变函数执行时的 this,即控制上下文。
      2. 都能用来调用其他对象的方法,并将该方法的 this 设置为指定的对象。(实际和第一条差不多)
    • 不同点
      1. call 需要逐个列出参数。
      2. 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)是用来装饰类方法中的参数的装饰器。参数装饰器允许我们为方法参数添加元数据,或者在参数被传递给方法之前执行一些额外的操作。


http://www.ppmy.cn/server/164579.html

相关文章

【贪心算法】在有盾牌的情况下能通过每轮伤害的最小值(亚马逊笔试题)

思路&#xff1a; 采用贪心算法&#xff0c;先计算出来所有的伤害值&#xff0c;然后再计算每轮在使用盾牌的情况下能减少伤害的最大值&#xff0c;最后用总的伤害值减去能减少的最大值就是最少的总伤害值 public static long getMinimumValue(List<Integer> power, int…

Java的类加载过程

类加载就是把类&#xff08;通常是.class文件的形式&#xff09;通过类加载器加载到 JVM 中&#xff0c;经过一系列的解析成可用的 class 类 二进制流的来源可能有&#xff1a; 编译后的.class文件 使用ASM、ByteBuddy等字节码生成工具创建的字节码 甚至可以从网络传输得到&a…

[c语言日寄]assert函数功能详解

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

一文掌握ADB的安装及使用

文章目录 一、什么是ADB&#xff1f;二、 安装ADB2.1 下载ADB2.2 配置环境变量 三、连接Android设备四、 常用ADB命令五、ADB高级功能5.1 屏幕截图和录制5.2 模拟按键输入5.3 文件管理5.4 系统设置管理5.5 系统操作指令5.6 日志操作指令5.7 APK操作指令5.8 设备重启和恢复 六、…

C++ 中用于控制输出格式的操纵符——setw 、setfill、setprecision、fixed

目录 四种操纵符简要介绍 setprecision基本用法 setfill的基本用法 fixed的基本用法 setw基本用法 以下是一些常见的用法和示例&#xff1a; 1. 设置字段宽度和填充字符 2. 设置字段宽度和对齐方式 3. 设置字段宽度和精度 4. 设置字段宽度和填充字符&#xff0c;结合…

Java 网络原理 ②-IP协议

这里是Themberfue 经过五节课的传输层协议的讲解&#xff0c;接下来我们将进入网络层协议——IP协议的讲解了~~~ IP协议 IP 相信大家在日常生活中或多或少都听过&#xff0c;你的IP地址是什么&#xff1f;192.168.0.1 ......✨IP 其实是个网络层协议&#xff0c;即互联网协议&…

【算法】回溯算法专题② ——组合型回溯 + 剪枝 python

目录 前置知识进入正题小试牛刀实战演练总结 前置知识 【算法】回溯算法专题① ——子集型回溯 python 进入正题 组合https://leetcode.cn/problems/combinations/submissions/596357179/ 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以…

电脑要使用cuda需要进行什么配置

在电脑上使用CUDA&#xff08;NVIDIA的并行计算平台和API&#xff09;&#xff0c;需要进行以下配置和准备&#xff1a; 1. 检查NVIDIA显卡支持 确保你的电脑拥有支持CUDA的NVIDIA显卡。 可以在NVIDIA官方CUDA支持显卡列表中查看显卡型号是否支持CUDA。 2. 安装NVIDIA显卡驱动…