TS的装饰器原理

news/2024/11/29 17:43:21/

对于前端而言,装饰器是一个陌生的概念,但是对于 Java、C# 等语言来说装饰器这一概念并不陌生。

所谓装饰器,就是一种特殊的类型声明,它可以被附加到「属性」、「类声明」、「方法」、「方法参数」上。

他的好处就是可以编写元信息以内省代码。看不懂?没关系,继续往下看。

我知道,网上的很多文章都看的让人十分痛苦,但我觉得我的文章并不会这样,我会尽量由浅入深的

装饰器模式

在说 TS 和 JS 的装饰器之前,我想先说说设计模式的一种——装饰器模式。

装饰器模式允许像一个现有的对象添加新的功能,但是又不改变其结构,是作为对现有类的一个 Wrapper(一个包裹)。

因为设计模式是在面向对象编程中发展出来的,而在 OOP 的代码中,我们理应遵循「多用组合,少用继承」的原则,所以装饰器模式通过动态的为对象添加额外的职责会比继承生成子类更为灵活。

接下来我会像 Angular 文档一样,说一个 Hero 的例子。

Hero 父类

首先我们需要一个 Hero 类:

 
  1. class Hero {
  2. constructor() {}
  3. attack() {}
  4. }

创建英雄

接着我们创建具体的英雄,我们就叫他 Hasaki 吧。

class Hasaki extends Hero {// 这里重写了 Hasaki 的攻击方法attack() {console.log('ha-sa-ki');}
}

英雄变身

我们设定英雄 Hasaki 拥有两个形态,并且每个形态拥有不同的技能与伤害。该怎么写代码?

难道写两个子类继承自 Hasaki 类?当然不

我们可以用依赖注入的思想:

 
class RedHasaki {constructor(hasaki) {this.hasaki = hasaki;}attack() {console.log('red!!! ha-sa-ki!!!');}
}
class BlueHasaki {constructor(hasaki) {this.hasaki = hasaki;}attack() {console.log('blue!!! ha-sa-ki!!!');}
}

这样一来,我们在 Hasaki 变身之后只需要这么写就可以了:

const redHasaki = new RedHaski(hasaki);

函数运行时间检测器

接下来我们再来写一个东西,来看看 Functional Programming 中的装饰器咋用。

先来制作一个时间检测器吧,用来检测函数的执行时间。

const startTime = +new Date();
doSomeThing();
const endTime = +new Date();
console.log(endTime - startTime);

当我们中间函数不是异步函数时,就可以通过 endTime 和 startTime 的差值计算出该函数操作消耗的时间。

当然了,这是最简陋的做法,我们应该去封装一下,来个高级点的做法,比如使用一个函数:

const timeRecorder = (fn) => {const startTime = +new Date();fn();const endTime = +new Date();console.log(endTime - startTime);
}timeRecorder(doSomething);

这样虽然封装了,可我们不满足,因为这样一来我们根本没有自己来调用 doSomething 呀!

或者,我们可以更高级一点,让函数 return 一个函数吧:

const timeRecorder = (fn) => {return () => {const startTime = +new Date();fn();const endTime = +new Date();console.log(endTime - startTime);}
}const doSomething = timeRecorder(foo);
doSomething();

这个时候我们其实就可以叫 timerRecorder 的这种写法为装饰器模式了。

装饰器的用法

我们上面这种写法虽然好,但是这么好的写法语言当然要自己自带一下啦!

TS 装饰器使用 @expression 这样的形式:

function timerRecorder(fn: Function) {...
}@timerRecorder
function doSomething() {......
}

这就等价于我们上一段代码。

组合装饰器

装饰器还可以组合使用:

@foo
@bar
function doSomething() {......
}

这个效果就相当于我们先使用 bar 装饰器,然后将返回的函数传入 foo 装饰器。

装饰器工厂

我们偶尔会看到有些装饰器后面会带上小括号,就像这样:@foo()

我们知道,装饰器的内部是一个函数,那么我们完全可以使用一个工厂函数去创建装饰器,就像这样:

 
  1. function decoratorFactory() {
  2. return function(fn: Function) {
  3. // 这里是装饰器
  4. }
  5. }
  6. @decoratorFactory()

这样我们的 @decoratorFactory() 就成为了一个装饰器。

那我们为什么需要装饰器工厂呢?

因为我们的装饰器可能会接受参数。写过 Angular 的同学应该知道,通常他的写法都是:

 
  1. @NgModule({
  2. imports: [...],
  3. ...
  4. })

如果是单纯的装饰器,我们是没办法传参数的,因此我们通过工厂函数+闭包来解决这一问题。

function decoratorFactory(param1: any, param2: any) {return function(fn: Function) {doSomething(param1, param2);fn()}
}

另外,写在类上的装饰器,装饰器会作用,且仅会作用于构造函数 constructor

 
  1. @foo
  2. class Phone() {
  3. constructor() {
  4. }
  5. ......
  6. }

也就是说,类装饰器可以用来「监视」、「修改」或者替换类的构造。

@expression 求值后必须是一个函数,他会在运行时被调用,装饰器的声明信息作为「参数」被传入。

最后,总结一下:

  1. 装饰器是对类、函数、属性之类的一种装饰,可以针对其添加一些额外的行为;
  2. 通俗的来说,就是在原有代码外层包装了一层处理逻辑;
  3. 装饰器是一种解决方案,而并非是狭义的 @xxxx
  4. 装饰器通常可以用于:类型判断、对返回值的排序、过滤,或者对函数添加节流、防抖等其他的功能性代码,各种各样的与函数逻辑本身无关的、重复性的代码。

PS:core-decorators 是一个封装了常用装饰器的 JS 库,它归纳了很多常用装饰器,可以参考一下,例如:

  • autobind:自动绑定 this,告别箭头函数和 bind;
  • readonly:将类属性设置为只读;
  • override:检查子类的方法是否正确覆盖了父类的同名方法;
  • debounce:防抖函数;
  • throttle:节流函数;
  • enumerable:让一个类方法变得可枚举;
  • nonenumerable:让一个类属性变得不可枚举;
  • time:打印函数执行耗时。

http://www.ppmy.cn/news/39165.html

相关文章

最优控制中不同情形下泛函取到极值的必要条件

最优控制中不同情形下泛函取到极值的必要条件最优控制中不同情形下泛函取到极值的必要条件引言一般问题1. t0t_0t0​ 固定,t1t_1t1​ 固定,x0x(t0)x_0x(t_0)x0​x(t0​) 固定,x1x(t1)x_1x(t_1)x1​x(t1​) 固定2. t0t_0t0​ 固定,…

【计算机组成原理】王道中央处理器学习笔记

声明:本文在经过 努力的clz同意的情况下进行了一定的借鉴。图片来源于王道,本文仅用于学习所用!王道计算机组成原理课代表 - 考研计算机 第五章 中央处理器 究极精华总结笔记_努力的clz的博客-CSDN博客CPU的功能和基本结构cpu的功能&#xff…

scala函数式编程

目录 不同范式对比: 1.面向对象编程 2.函数式编程 2.1函数基本语法 2.2函数和方法的区别 核心概念: 2.3函数定义 2.4函数参数 2.5 函数至简原则 2.6.高阶函数 三.偏函数 四.柯里化函数 五.递归函数 递归函数注意点: 六.控制抽象 1…

RT-Thread GD32F4xx ADC驱动记录

目录 1、添加GD32F4xx ADC驱动1.1 添加menuconfig ADC选项1.2 添加drv_adc.c/.h1.3 应用测试2、针对电路板的多路转换+DMA2.1 添加宏定义及DMA结构体定义2.2 GPIO init2.3 gd32_adc_dma_enabled2.4 gd32_adc_dma_convert2.5 gd32_adc_ops2.6 应用测试1、添加GD32F4xx ADC驱动 …

【SQL 必知必会】- 第八课 使用函数处理数据

目录 函数 函数带来的问题 可移植(portable) 是否应该使用函数? 使用函数 文本处理函数 SOUNDEX 支持 日期和时间处理函数 数值处理函数 函数 函数带来的问题 与几乎所有DBMS 都等同地支持SQL 语句(如SELECT)不同&am…

企业级 AI 研发的正确姿势:开源 LLM + LoRA轻松提效

本文的阅读成本很低,不需要大量地先验 AI 知识。作为纯纯的软件工程师,我们发现:学懂基本的 AI Prompt 原理与模式,不懂 LLM (大语言模型)算法,你也可以轻易驾驭 LoRA (Low-Rank Ada…

php+mysql仓储进销存仓库管理系统

仓库的管理的操作自动化和信息的电子化,全面提高了仓库的管理水平。随着我国改革开放的不断深入,经济飞速的发展,企业要想生存、发展,要想在激烈的市场竞争中立于不败之地,没有现代化的管理是万万不行的,仓…

How to install nacos/nacos-server:v2.1.2-slim with docker

今天给大家介绍一下如何基于Docker的nacos/nacos-server:v2.1.2-slim镜像安装nacos 1、Data Source 我们需要从nacos的github官网下载nacos 2.12发布包 nacos-server-2.1.2.tar.gznacos-server-2.1.2.zip 这里以nacos-server-2.1.2.tar.gz为例来介绍,解压后我们…