TypeScript21:装饰器

本文详细介绍了JavaScript装饰器的概念,包括其在解决关注点分离和重复代码问题中的作用,以及类装饰器、成员装饰器(属性和方法)的实现方式。同时探讨了如何在TypeScript中使用装饰器,以及装饰器在参数、依赖注入和AOP方面的应用。
摘要由CSDN通过智能技术生成

一、装饰器概述

  • 面向对象的概念( java :注解, c# :特征), decorator
  •  angular 大量使用, react 中也会用到;
  • 目前 JS 支持装饰器目前处于建议征集的第二阶段。

装饰器,能够带来额外的信息量,可以达到分离关注点的目的。 

解决的问题:

  • 关注点的问题:在定义某个东西时,应该最清楚该东西的情况;
  • 重复代码的问题。

 上述两个问题产生的根源:某些信息,在定义时,能够附加的信息量有限。

装饰器的作用: 

为某些属性、类、参数、方法提供元数据信息( metadata )。

元数据:描述数据的数据。

class User { 
  @require
  @range(3, 5)
  @description("账号")
  loginid: string = "123456"; // 描述是:账号,验证规则:1.必填,2.必须是3-5个字符

  loginpwd: string = "123456"; // 必须是6-12个字符
}

/**
 * 统一的验证函数
 * @param obj
 */
function validate(obj: object) {
  for (let key in obj) {
    let value = (obj as any)[key];
    // 验证规则
  }
}

装饰器的本质:

JS 中,装饰器是一个函数。(装饰器是要参与运行的)

装饰器可以修饰:

  • 成员(属性+方法)
  • 参数

二、类装饰器 

类装饰器的本质是一个函数,该函数接收一个参数,表示类本身(构造函数本身)

使用装饰器时:@得到一个函数,把函数调用返回的结果作为装饰器。

在 TS 中,如何约束一个变量为类:

  • Function
  • new(参数)=>object

在 TS 中使用装饰器:需要开启 "experimentalDecorators": true

装饰器函数的运行时间:在类定义后直接运行。

类装饰器可以具有的返回值:

  •  void :仅运行函数;
  • 返回一个新的类:会将新的类替换掉装饰目标。
function test(target:new()=>object) { 
  console.log(target);
}
@test
class A { 

}
// [class A]

在装饰器中返回类(不建议): TS 中使用得不到类型检查。

function test(target:new()=>object) { 
  return class B extends target { 

  }
}
@test
class A { 

}
const c = new A()
console.log(c) // B {}

在 TS 中如何约束构造函数:

function test(target:new(...args:any[])=>object) { 
  console.log(target);
}
@test
class A { 
  prop1: string = 'prop1';
  constructor(public prop2:string,public prop3:string) { }
}

如果将装饰器当成函数来调用并传递参数,此时由于装饰器需要一个返回值,所以可以这样使用:

function test(str: string) {
  return function (target:new(...args:any[])=>object) { 
    console.log(str,target);
  }
}
@test("这是一个类")
class A { 
  prop1: string = 'prop1';
  constructor(public prop2:string,public prop3:string) { }
}
// 这是一个类 [class A]

多个装饰器的情况:会按照后加入先调用的顺序调用,装饰器的调用是从下往上的

type constructor = new (...args:any[]) => object
function d1(target:constructor) { 
  console.log("d1");
}
function d2(target:constructor) { 
  console.log("d2");
}
@d1
@d2
class A { 
  prop1: string = 'prop1';
  constructor(public prop2:string,public prop3:string) { }
}
// d2
// d1

将装饰器当作函数来调用时:会按照后加入先调用的顺序调用

type constructor = new (...args:any[]) => object
function d1() { 
  console.log("d1");
  return function (target:constructor) { 
    console.log("d1 decorator");
  }
}
function d2() { 
  console.log("d2");
  return function (target:constructor) { 
    console.log("d2 decorator");
  }
}
@d1()
@d2()
class A { 
  prop1: string = 'prop1';
  constructor(public prop2:string,public prop3:string) { }
}
// d1
// d2
// d2 decorator
// d1 decorator

三、成员装饰器 

1.属性装饰器

 属性装饰器也是一个函数,该函数需要两个参数:

  1. 如果是静态属性,则为类本身;如果是实例属性,则为类的原型;
  2. 固定为一个字符串,表示属性名。
function d(target: any, key: string) { 
  console.log(target, key, target === A.prototype);
}
class A { 
  @d
  prop1: string = "1"
  @d
  prop2: string = "2"
}
// {} prop1 true
// {} prop2 true

此时,参数 target A 的原型。

(1)成员装饰器实例属性的用法:

function d(target: any, key: string) { 
  // console.log(target, key, target === A.prototype);
  if (!target.__props) { 
    target.__props = [];
  }
  target.__props.push(key);
}
class A { 
  @d
  prop1: string = "1"
  @d
  prop2: string = "2"
}
console.log((A.prototype as any).__props); // [ 'prop1', 'prop2' ]
function d(target: any, key: string) { 
  // console.log(target, key, target === A.prototype);
  if (!target.__props) { 
    target.__props = [];
  }
  target.__props.push(key);
}
class A { 
  @d
  prop1: string = "1"
  @d
  prop2: string = "2"
}
const a = new A()
console.log((a as any).__props); // [ 'prop1', 'prop2' ]

(2)成员装饰器静态属性的用法:

function d(target: any, key: string) { 
  console.log(target, key, target === A.prototype);
}
class A { 
  @d
  prop1: string = "1"
  @d
  static prop2: string = "2"
}
// {} prop1 true
// [class A] { prop2: '2' } prop2 false

使用函数调用装饰器: 装饰器工厂

function d() { 
  return function (target: any, key: string) { 
  console.log(target, key, target === A.prototype);
    
  }
}
class A { 
  @d()
  prop1: string = "1"
  @d()
  static prop2: string = "2"
}

2.方法装饰器

方法装饰器也是一个函数,该函数需要三个参数:

  1. 如果是静态方法,则为类本身;如果是实例方法,则为类的原型;
  2. 固定为一个字符串,表示方法名;
  3. 属性描述对象。

可以有多个装饰器修饰。 

function d() { 
  return function (target: any, key: string,descriptor:PropertyDescriptor) { 
    console.log(target, key,descriptor);
  }
}
class A { 
  @d()
  method1(){ }
}
// {} method1 {
//   value: [Function: method1],
//   writable: true,
//   enumerable: false,
//   configurable: true
// }

 默认情况下,装饰器中的描述属性 enumerable false ,代表了类中的方法不参与循环:

function d() { 
  return function (target: any, key: string,descriptor:PropertyDescriptor) { 
    console.log(target, key,descriptor);
  }
}
class A { 
  @d()
  method1(){ }
}
const a = new A();
for (const key in a) {
  console.log(key); // 这里没有输出
}

可以在装饰器内部将属性 enumerable 改为 true ,让方法参与循环:好处在于这个功能是通用的,当想要参与遍历的方法很多时,只要在方法前加上装饰器。

function d() { 
  return function (target: any, key: string,descriptor:PropertyDescriptor) { 
    console.log(target, key, descriptor);
    descriptor.enumerable = true
  }
}
class A { 
  @d()
  method1(){ }
}
const a = new A();
for (const key in a) {
  console.log(key); // method1
}

四、补充 

 1.参数装饰器

依赖注入、依赖倒置。 

要求函数有三个参数:

  1. 如果方法是静态的,则为类本身;如果方法是实例方法,则为类的原型;
  2. 方法名称
  3. 在参数列表中的索引
function test(target: any, method: string, index: number) { 
  console.log(target, method, index);
}
class MyMath{ 
  sum(a: number, @test b: number) { 
    return a + b
  }
}
// {} sum 1

2.关于TS自动注入的元数据

如果安装了 reflect-metadata ,并且导入了该库,并且在某个成员上添加了元数据并且启用了emitDecoratorMetadata
TS 在编译结果中,会将约束的类型,作为元数据加入到相应位置。

这样一来, TS 的类型检查(约束)将有机会在运行时进行。

3.AOP(aspect oriented programming)

编程方式,属于面向对象开发, 

将一些在业务中共同出现的功能块,横向切分,已达到分离关注点的目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猛扇赵四那半好嘴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值