TypeScript装饰器如此简单

一.前言


🐻有一个空房子,如果你给这个房子放入了床,它就成了一个休息室,如果你给它放入了一台游戏机,它就变成了一个游戏厅,放入了一个茶几又变成了一个客厅,其实这里不同的家具就是一个装饰器,不同的家具给这个房子扩展了不同的功能,这个概念其实比较容易理解,装饰器字如其名就是装饰的作用,但是当我们使用装饰器的时候有会有很多疑问:为什么?我给函数加了装饰器它就会在调用函数之前输出了装饰器中输出的一些东西?为什么它能将我类中的属性装饰成仅仅可读的状态?其实理解这个问题我们可以简单的理解,就像类每次实例化的时候都会调用构造函数一样,是在构造器底层这么处理的,我们先保证会用,之后可以研究底层到底做了什么,这篇文章会带你从装饰器的使用到底层的操作一网打尽,一次性解决装饰器的疑问。

二.装饰器的种类和理解


🫥有一天祝融在写代码,共工过来告诉它,需要增加某个功能,祝融一看,懵逼了对这个改动会有很多代码的侵入和修改,两人直接大打出手,怒撞不周山,女娲看到了这一幕觉得这样不利于天下的和平与稳定,于是,念动咒语"让TS中增添一个特性,直接在类和类的成员,属性,参数,方法上面添加@符号,并且在某个地方进行定义一个函数为这个@声明功能"于是,TS世界中多了一个叫做装饰器的特性,至于为什么能这样写这样用,等你修为更高一点的时候再去领悟吧。


🫥工欲善其事必先利其器,首先我们来搞定一下对装饰器支持的TS环境,我们在终端中输入tsc --init然后放开下面两项配置,因为装饰器是一种新的特性并没有完全进行支持,其次我们最好安装下ts-node安装方式非常简单npm install ts-node -g

js 复制代码
"experimentalDecorators": true,
"emitDecoratorMetadata": true

🤡首先我们来看下装饰器的种类。

  1. 类装饰器:target指的就是这个类本身。
js 复制代码
function Logger(target: any) {
  console.log("Logging...");
  console.log(target);
}

@Logger
class MyClass {
  // 类实现
}

// 输出内容 :Logging...  MyClass
  1. 方法装饰器:target指的是类的原型,key方法的名称,descriptor:方法的描述。
js 复制代码
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.log("Logging...");
    console.log(target);
    console.log(key);
    console.log(args);
    return originalMethod.apply(this, args);
  };
}

class MyClass {
  @Log
  myMethod() {
    // 方法实现
  }
}

 // Logging...
 // [object Object]  // target:类的原型对象
 // myMethod          // key:方法名称
 // []                // args:方法的参数,这里为空数组
  1. 属性装饰器:target:类的原型对象,key属性的名称
js 复制代码
function Readonly(target: any, key: string) {
  const descriptor: PropertyDescriptor = {
    writable: false
  };
  Object.defineProperty(target, key, descriptor);
}

class MyClass {
  @Readonly
  myProperty = 123;
}
  1. 参数装饰器:target:类的原型对象,key:方法的名称,parameterIndex:参数的索引
js 复制代码
function Log(target: any, key: string, parameterIndex: number) {
  console.log("Logging...");
  console.log(target);
  console.log(key);
  console.log(parameterIndex);
}

class MyClass {
  myMethod(@Log param1: string, @Log param2: number) {
    // 方法实现
  }
}

 // Logging...
 // [object Object]  // target:类的原型对象
 // myMethod          // key:方法名称
 // 0                 // parameterIndex:参数的索引,这里是第一个参数
 // Logging...
 // [object Object]  // target:类的原型对象
 // myMethod          // key:方法名称
 // 1                 // parameterIndex:参数的索引,这里是第二个参数

💡Tips:在实际开发中我们一般使用的是框架或者系统提供的装饰器。

👹下面我们来看下他们的加载顺序和执行流程(装饰器是比被装饰对象先执行的)

  1. 类装饰器的执行顺序是从上到下的,但装饰器的加载顺序是从下到上的。也就是说,最下面的装饰器会最先执行。
  2. 方法装饰器的执行顺序是从上到下的,但装饰器的加载顺序是从下到上的。也就是说,离方法定义最近的装饰器最先执行。
  3. 属性装饰器的执行顺序是从上到下的,但装饰器的加载顺序是从下到上的。也就是说,离属性定义最近的装饰器最先执行。
  4. 参数装饰器的执行顺序是从左到右的,但装饰器的加载顺序是从右到左的。也就是说,参数从左到右依次执行对应的装饰器函数,但装饰器函数的加载顺序是从右到左的。

三.打破对装饰器的幻想


🤡刚看到装饰器是不是觉得很神奇,其实没啥神奇的,我们也可以看成类似于class的语法糖,装饰器就是如下这种操作的语法糖,只不过是机器代替了手动的创建方式,如下(以类装饰器为例)

js 复制代码
const MoveDecorator: ClassDecorator = (constructor: Function) => {
    constructor.prototype.name = 'zpj'
    constructor.prototype.getPosition = (): { x: number, y: number } => {
        return { x: 100, y: 100 }
    }
}

class Tank {
    constructor() {
        console.log('构造函数');
    }
}

MoveDecorator(Tank);
const tank = new Tank()
console.log(tank.getPosition());

四.装饰器工厂


🦝上面我们学习了装饰器的基本使用,我们知道装饰器本质上就是一个函数,但是我们如果单独写一个函数的话装饰器功能会比较固定和单一,我们可以使用工厂模式来根据不同的内容来返回不同的装饰器。

js 复制代码
const MusicDecorator = (type: string): ClassDecorator => {
    switch (type) {
        case 'player':
            return (constructor: Function) => {
                constructor.prototype.playMusic = (): void => {
                    console.log(`播放【海阔天空】音乐`);
                }
            }
            break;
        default:
            return (constructor: Function) => {
                constructor.prototype.playMusic = (): void => {
                    console.log(`播放【aaa】音乐`);
                }
            }

    }
}

@MusicDecorator('tank')
class Tank {
    constructor() {
    }
}
const tank = new Tank();
(<any>tank).playMusic();

@MusicDecorator('player')
class Player {
}

const xj: Player = new Player();
(xj as any).playMusic()

五.总结


🐻这篇文章主要学习了装饰器相关的内容,装饰器是一个新的特性,但是在Angular中和Nest中已经广泛使用了,装饰器并不是一个特别神奇的特性,它的本质是一个函数,我们可以通过现实的例子来理解和使用它,总之装饰器的使用在未来的开发中是一个必然的趋势。

相关推荐
sg_knight几秒前
VSCode如何修改默认扩展路径和用户文件夹目录到D盘
前端·ide·vscode·编辑器·web
一个处女座的程序猿O(∩_∩)O10 分钟前
完成第一个 Vue3.2 项目后,这是我的技术总结
前端·vue.js
mubeibeinv10 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
逆旅行天涯17 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
m0_7482552638 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_748254881 小时前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl