当前位置: 首页 > article >正文

【TypeScript】扩展:装饰器

文章目录

    • 装饰器
      • 一、类装饰器
          • 1. 基本用法
          • 2. 装饰器返回值
          • 3. 构造类型
          • 4. 替换被装饰的类
      • 二、装饰器工厂
      • 三、装饰器组合
      • 四、属性装饰器
      • 五、方法装饰器
      • 六、访问器装饰器
      • 七、参数装饰器

装饰器

装饰器本质是一种特殊函数,可以对类、属性、方法、参数进行扩展,使代码更简洁。

一、类装饰器

1. 基本用法

Demo函数会在Person类定义时即刻执行;target参数是被装饰的类,即Person

function Demo(target:Function){
    console.log(target)
}
@Demo //这句话相等于 Demo(Person)
class Person {
    constructor(public name:string,public age:number){ }
}
  • 应用举例(追加方法)

    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关键字调用的类

/*   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装饰器,可以给实例添加一个属性,用于记录实例对象创建的时间,再添加方法读取时间。

    type Constructor = new (...args: any[]) => {} //构造类型
    //接口(自动合并的特性)
    interface Person {
        printDate():void //让Person知道自己多了一个函数
    }
    function SetTime<T extends Constructor>(target: T) { //装饰的必须是可以new的类
        return class extends target{ //继承原类,名字不变
            createTime: Date
            constructor(...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指定次数

    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属性?
    答:静态属性是类本身的属性,而不是类的实例的属性。通过类名可以访问静态属性,而无法通过实例来访问

  • 现在有需求,需要在类属性的值修改之际,就打印出修改的信息。这时候就需要用到属性装饰器

    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: number
        constructor(name: number) {
            this.age = name //当访问this.age时,先在原型链上找是否有这个属性,再做修改
        }
    }
    
  • 测试用例

    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值是被装饰的方法
  • 应用举例:在函数执行时,打印函数执行日志

    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}函数`) }
    }
    
  • 测试用例

    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。

  • 应用案例:定义一个简单的装饰器,在访问某个属性时打印日志

    function log(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {
        const originalGet = descriptor.get;
        const originalSet = descriptor.set;
        // 修改 getter
        descriptor.get = function() {
            console.log(`Getting value of ${String(key)}`);
            return originalGet?.call(this);
        };
        // 修改 setter
        descriptor.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 = '';
        @log
        get name() {
            return this._name;
        }
        @log
        set 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.kler.cn/a/530111.html

相关文章:

  • 使用 Numpy 自定义数据集,使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数
  • 松灵机器人 scout ros2 驱动 安装
  • UE编辑器工具
  • MP4分析工具
  • linux 函数 sem_init () 信号量、sem_destroy()
  • C++:虚函数与多态性习题2
  • 在Arm芯片苹果Mac系统上通过homebrew安装多版本mysql并解决各种报错,感谢deepseek帮助解决部分问题
  • Python 原子操作:使用 `atomic` 模块保证线程安全
  • web前端12--表单和表格
  • 科技快讯 | 领英“隐私风波”告一段落;华为余承东智驾 1345 公里返工,称智界 R7 打赢“鸡蛋保卫战”;谷歌翻译将增“提问”功能
  • 利用Spring Batch简化企业级批处理应用开发
  • 【漫话机器学习系列】075.隐含层(Hidden Layer)
  • Git如何避免推送.idea文件夹
  • 使用 vllm 搭建推理加速大模型服务
  • OpenAI 实战进阶教程 - 第二节:生成与解析结构化数据:从文本到表格
  • 想品客老师的第天:类
  • Java集合+并发(部分)
  • MultiResUNet学习笔记(2019 Neural Networks【SCI 1区】)
  • 用结构加法3ax+1预测第4点的分布
  • 掌握Spring MVC异常处理的艺术
  • ICLR 2025收录论文:为什么动作分块对于机器人灵活性至关重要?
  • makailio-alias_db模块详解
  • 蓝桥杯备考:六大排序算法
  • Hive重点面试题
  • #define,源文件与头文件,赋值表达式
  • Java项目: 基于SpringBoot+mybatis+maven+mysql实现的疾病防控综合管理系统(含源码+数据库+毕业论文)