摸鱼偷学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);

结尾

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

相关推荐
用户479492835691516 分钟前
改了CSS刷新没反应-你可能不懂HTTP缓存
前端·javascript·面试
还好还好不是吗32 分钟前
老项目改造 vue-cli 2.6 升级 rsbuild 提升开发效率300% upupup!!!
前端·性能优化
sumAll35 分钟前
别再手动对齐矩形了!这个开源神器让 AI 帮你画架构图 (Next-AI-Draw-IO 体验)
前端·人工智能·next.js
OpenTiny社区36 分钟前
2025OpenTiny星光ShowTime!年度贡献者征集启动!
前端·vue.js·低代码
wangan0941 小时前
不带圆圈的二叉树
java·前端·javascript
狗哥哥1 小时前
从零到一:打造企业级 Vue 3 高性能表格组件的设计哲学与实践
前端·vue.js·架构
疯狂平头哥1 小时前
微信小程序真机预览-数字不等宽如何解决
前端
Drift_Dream1 小时前
前端趣味交互:如何精准判断鼠标从哪个方向进入元素?
前端
hqk1 小时前
鸿蒙ArkUI:状态管理、应用结构、路由全解析
android·前端·harmonyos
米思特儿林1 小时前
NuxtImage 配置上传目录配置
前端