聊聊Typescript中的设计模式——装饰器篇(decorators)

栏目: 后端 · 发布时间: 6年前

内容简介:随着Typescript的普及,在KOA2和nestjs等nodejs框架中经常看到类似于java spring中注解的写法。本文从装饰模式出发,聊聊Typescipt中的装饰器和注解。原文地址在:欢迎star

  随着Typescript的普及,在KOA2和nestjs等nodejs框架中经常看到类似于java spring中注解的写法。本文从装饰模式出发,聊聊Typescipt中的装饰器和注解。

  • 什么是装饰者模式
  • Typescript中的装饰器
  • Typescript中的注解
  • 总结

原文地址在: github.com/fortheallli…

欢迎star

一、什么是装饰者模式

  最近在看nestjs等支持Typescript的node框架,经常看到这样一种写法:

import { Controller, Get } from '@nestjs/common';

@Controller('cats')

export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}
复制代码

  上述代码定义了一个处理url为“/cats”的控制器,该控制器对于url为“/cats”的get方法执行findAll()函数,返回相应的字符串。

  在上述的代码中,用@Controller('cats')修饰CatsController类,通过@Get来修饰类中的findAll方法,这就是典型的装饰者模式。通过@Controller('cats')和@Get修饰后的类CatsController,简单来说,就是拥有了丰富的“内涵”。

下面看看具体装饰者模式的定义:

我们知道继承模式是丰富子元素“内涵”的一种重要方式,不管是继承接口还是子类继承基类。而装饰者模式可以在不改变继承关系的前提下,包装先有的模块,使其内涵更加丰富,并不会影响到原来的功能。与继承相比,更加的灵活。

javascript中的装饰器处于建议征集的第二阶段,通过babel和Typescrit都可以实现装饰器的语法。

二、Typescript中的装饰器

Typescript中的装饰器与类相关,分别可以修饰类的实例函数和静态函数、类本身、类的属性、类中函数的参数以及类的set/get存取器,下面来意义介绍。

(1)、类方法的装饰器

下面来介绍一下用装饰器来修饰函数,首先来看一个例子:

let temple;
function log(target, key, descriptor) {
  console.log(`${key} was called!`);
  temple = target;
}
class P {
    @log
    foo() {
      console.log('Do something');
    }
}

const p = new P()
p.foo()
console.log(P.prototype === temple) //true
复制代码

上述是实例方法foo中我们用log函数修饰,log函数接受三个参数,通过P.prototype === temple(target)可以判断,在类的实例函数的装饰器函数第一个参数为类的原型,第二个参数为函数名本身,第三个参数为该函数的描述属性。

具体总结如下,对于类的函数的装饰器函数,依次接受的参数为:

  • target:如果修饰的是类的实例函数,那么target就是类的原型。如果修饰的是类的静态函数,那么target就是类本身。
  • key: 该函数的函数名。
  • descriptor:该函数的描述属性,比如 configurable、value、enumerable等。

从上述的例子中我们可以看到,用装饰器来修饰相应的类的函数十分方便:

@log
foo() {
  ...
}
复制代码

(2)、类的装饰器

装饰函数也可以直接修饰类:

let temple
function foo(target){
   console.log(target);
   temple = target
}
@foo
class P{
   constructor(){
     
   }
}

const p = new P();
temple === P //true
复制代码

当装饰函数直接修饰类的时候,装饰函数接受唯一的参数,这个参数就是该被修饰类本身。上述的例子中,输出的target就是类P的本身。

此外,在修饰类的时候,如果装饰函数有返回值,该返回值会重新定义这个类,也就是说当装饰函数有返回值时,其实是生成了一个新类,该新类通过返回值来定义。

举例来说:

function foo(target){
   return class extends target{
      name = 'Jony';
      sayHello(){
         console.log("Hello "+ this.name)
      }
   }
}
@foo
class P{
   constructor(){
     
   }
}

const p = new P();
p.sayHello(); // 会输出Hello Jony
复制代码

上面的例子可以看到,当装饰函数foo有返回值时,实际上P类已经被返回值所代表的新类所代替,因此P的实例p拥有sayHello方法。

(3)、类的属性的装饰器

下面我们来看类的属性的装饰器,装饰函数修饰类的属性时,在类实例化的时候调用属性的装饰函数,举例来说:

function foo(target,name){
   console.log("target is",target);
   console.log("name is",name)
}
class P{
   @foo
   name = 'Jony'
}
const p = new P();
//会依次输出 target is f P()  name is Jony
复制代码

这里对于类的属性的装饰器函数接受两个参数,对于静态属性而言,第一个参数是类本身,对于实例属性而言,第一个参数是类的原型,第二个参数是指属性的名字。

(4)、类函数参数的装饰器

接着来看类函数参数的装饰器,类函数的参数装饰器可以修饰类的构建函数中的参数,以及类中其他普通函数中的参数。该装饰器在类的方法被调用的时候执行,下面来看实例:

function foo(target,key,index){
   console.log("target is",target);
   console.log("key is",key);
   console.log("index is",index)
}
class P{
   test(@foo a){
   }
}
const p = new P();
p.test("Hello Jony")
// 依次输出 f P() , test , 0 
复制代码

类函数参数的装饰器函数接受三个参数,依次为类本身,类中该被修饰的函数本身,以及被修饰的参数在参数列表中的索引值。上述的例子中,会依次输出 f P() 、test和0。再次明确一下修饰函数参数的装饰器函数中的参数含义:

  • target: 类本身
  • key:该参数所在的函数的函数名
  • index: 该参数在函数参数列表中的索引值

从上面的Typescrit中在基类中常用的装饰器后,我们发现:

装饰器可以起到分离复杂逻辑的功能,且使用上极其简单方便。与继承相比,也更加灵活,可以从装饰类,到装饰类函数的参数,可以说武装到了“牙齿”。

三、Typescript中的注解

在了解了Typescrit中的装饰器之后,接着我们来看Typescrit中的注解。

什么是注解,所谓注解的定义就是:

为相应的类附加元数据支持

所谓元数据可以简单的解释,就是修饰数据的数据,比如一个人有name,age等数据属性,那么name和age这些字段就是为了修饰数据的数据,可以简单的称为元数据。

元数据简单来说就是可以修饰某些数据的字段。下面给出装饰器和注解的解释和区别:

  • 装饰器:定义劫持,可以对类,类的方法,类的属性以及类的方法的入参进行修改。不提供元数据的支持。
  • 注解:仅提供元数据的支持。

两者之间的联系:

通过注解添加元数据,然后在装饰器中获取这些元数据,完成对类、类的方法等等的修改,可以在装饰器中添加元数据的支持,比如可以可以在装饰器工厂函数以及装饰器函数中添加元数据支持等

(1)、Typescript中的元数据操作

可以通过reflect-metadata包来实现对于元数据的操作。首先我们来看reflect-metadata的使用,首先定义使用元数据的函数:

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
复制代码

这里的format可以作为装饰器函数的工厂函数,因为format函数返回的是一个装饰器函数,上述的方法定义了元数据Sysmbol("format"),用Sysmbol的原因是为了防止元数据中的字段重复,而format定义了取元数据中相应字段的功能。

接着我们来在类中使用相应的元数据:

class Greeter {
    @format("Hello, %s")
    name: string;

    constructor(name: string) {
        this.name = message;
    }
    sayHello() {
        let formatString = getFormat(this, "name");
        return formatString.replace("%s", this.name);
    }
}

const g = new Greeter("Jony");
console.log(g.sayHello());
复制代码

在上述中,我们在name属性的装饰器工厂函数,执行@format("Hello, %s"),返回一个装饰器函数,且该装饰器函数修饰了Greeter类的name属性,将“name”属性的值写入为"Hello, %s"。

然后再sayHello方法中,通过getFormat(this,"name")取到formatString为“Hello,%s”.

四、总结

通过装饰器,可以方便的修饰类,以及类的方法,类的属性等,相比于继承而言更加灵活,此外,通过注解的方法,可以在Typescript中引入元数据,实现元编程等。特别是在angularjs、nestjs中,大量使用了注解,特别是nestjs构建了类似于java springMVC式的web框架。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法:C语言实现

算法:C语言实现

塞奇威克 / 霍红卫 / 机械工业出版社 / 2009-10 / 79.00元

《算法:C语言实现(第1-4部分)基础知识、数据结构、排序及搜索(原书第3版)》细腻讲解计算机算法的C语言实现。全书分为四部分,共16章。包括基本算法分析原理,基本数据结构、抽象数据结构、递归和树等数据结构知识,选择排序、插入排序、冒泡排序、希尔排序、快速排序方法、归并和归并排序方法、优先队列与堆排序方法、基数排序方法以及特殊用途的排序方法,并比较了各种排序方法的性能特征,在进一步讲解符号表、树等......一起来看看 《算法:C语言实现》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器