摸鱼偷学TS:装饰器

浅聊一下

装饰器(decorator)最早在 Python 中被引入,它的主要作用是给一个已有的方法或类扩展一些新的行为,而不是去直接修改它本身.

ES2015 进入 Class 之后,当我们需要在多个不同的类之间共享或者扩展一些方法或行为的时候,代码会变得错综复杂,极其不优雅,这也就是装饰器被提出的一个很重要的原因

装饰器

Typescript 中我们需要在 tsconfig.json 里面开启支持选项

ts 复制代码
// tsconfig.json
"experimentalDecorators": true

类装饰

想象一个场景,有一个Person类和一个addAge函数,addAge函数可以给Person添加一个年龄的属性,我们该如何来实现?

ts 复制代码
function addAge(constructor: Function) {
    constructor.prototype.age = 18;
}
class Person {
    name: string;
    age!: number;
    constructor() {
        this.name = 'juejin';
    }
}
addAge(Person);
let person = new Person();
console.log(person.age);//18
  • 我们首先定义了 addAge 函数,它接受一个构造函数作为参数,并给这个构造函数的原型添加一个名为 age 的属性,赋值为 18
  • 然后我们定义了 Person 类,其中包含一个 name 属性和一个构造函数,用于初始化 name。此时,我们不再需要在类定义中声明 age 属性,因为它将通过原型链被添加。
  • 接下来,我们调用 addAge(Person),将 Person 构造函数传递给 addAge 函数,这样就会在 Person 的原型上添加 age 属性。

注意这里我们并没有使用装饰器,那么使用装饰器又该如何来写

ts 复制代码
function addAge(constructor: Function) {
    constructor.prototype.age = 18;
}
@addAge
class Person {
    name: string;
    age!: number;
    constructor() {
        this.name = 'juejin';
    }
}
let person = new Person();
console.log(person.age);//18

Person 类定义之前,使用了 @addAge 装饰器。这相当于在类定义完成后立即执行 addAge(Person),从而向 Person 的原型上添加了 age 属性。

总的来说,装饰器提供了一种优雅的方式来修改或增强类的行为,而无需改动类的原始代码。

属性、方法装饰器

一个类的属性和方法也可以被装饰

ts 复制代码
function method(target: any, key: string,descriptor: PropertyDescriptor) {
    console.log(target);
    console.log('prop ' + key);
    console.log('desc' + JSON.stringify(descriptor) + '\n\n');
    descriptor.writable = false;
}
class Person {
    name: string;
    constructor() {
        this.name = 'juejin';
    }
    @method
    say(){
        return 'instance method'
    }
    @method
    static run() {
        return 'static method'
    }
}
let person = new Person();
person.say = function() {
    return 'edit'
}
console.log(person.say());
  1. 首先定义了一个装饰器函数,接受三个参数
ts 复制代码
function method(target: any, key: string, descriptor: PropertyDescriptor) {
    console.log(target); // 打印目标对象(对于实例方法,是类的原型;对于静态方法,是类本身)
    console.log('prop ' + key); // 打印被装饰的方法名
    console.log('desc' + descriptor + '\n\n'); // 打印方法的描述符
    descriptor.writable = false; // 设置描述符的writable属性为false,尝试修改方法会失败
}
  • target: 对于实例方法,它是类的原型;对于静态方法,它是类本身。
  • key: 被装饰的方法的名称。
  • descriptor: 方法的属性描述符,它是一个对象,包含了该属性的特性(如 value, writable, configurable, enumerable 等)。
  1. Person类

在Person类中的say方法和run方法前加上装饰器函数

  1. 调用

想修改Person类的say方法,并且调用say方法查看是否改动

先来看看结果

首先可以看到我们并没有调用run方法,可是run方法被调用了

这是因为在类定义的时候,就会调用装饰器函数,即使我们没有实例化类对象。 而报错是因为我们无法修改一个只读属性

在属性/方法的装饰器定义过程中,与 class 的装饰器不同,我们的 method 函数中的参数变为了三个 target propertyKey descriptor.这三个参数正是源于Object.defineProperty

上面的方法装饰器代码相当于下面:

ts 复制代码
let descriptor = {
    value: function() { return 'instance method'},
    enumerable: false,
    configurable: true,
    writable: true
};

descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;

Object.defineProperty(Cat.prototype, "say", descriptor);

结尾

写到这里,该去吃饭了...

相关推荐
小心我捶你啊1 小时前
数据采集和Web解锁不是一回事,从用途到规则区分
前端·爬虫·网络协议
hunterandroid1 小时前
[鸿蒙从零到一] ArkUI 基础组件实战:Text、Image、Button 与 TextInput
前端
fsssb2 小时前
Chromium 源码学习笔记(五):一次点击,在进入 JS 之前先经历了什么?
前端
hunterandroid2 小时前
WorkManager 可靠性实战:唯一任务、重试与幂等设计
android·前端
鹿目2 小时前
使用 Vant CLI 搭建 UI 组件库:从 H5 到小程序跨平台
前端·javascript
默_笙2 小时前
😭 我花了两小时找了一个数据结构 bug,才把"迷你 Cursor"跑起来,绝望(bushi)
前端·javascript
竹林8182 小时前
wagmi v2 监听合约事件踩坑记:从 `useContractEvent` 到 `watchContractEvent`,我重构了三版才搞定实时数据流
前端·javascript
天若有情6732 小时前
纯HTML+Tailwind单页作品集网站——零框架静态前端完整源码
前端·html
WebGirl3 小时前
H5唤起app前端实现方案
前端