一、装饰器概述
- 面向对象的概念( 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.属性装饰器
属性装饰器也是一个函数,该函数需要两个参数:
- 如果是静态属性,则为类本身;如果是实例属性,则为类的原型;
- 固定为一个字符串,表示属性名。
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.方法装饰器
方法装饰器也是一个函数,该函数需要三个参数:
- 如果是静态方法,则为类本身;如果是实例方法,则为类的原型;
- 固定为一个字符串,表示方法名;
- 属性描述对象。
可以有多个装饰器修饰。
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.参数装饰器
依赖注入、依赖倒置。
要求函数有三个参数:
- 如果方法是静态的,则为类本身;如果方法是实例方法,则为类的原型;
- 方法名称
- 在参数列表中的索引
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)
编程方式,属于面向对象开发,
将一些在业务中共同出现的功能块,横向切分,已达到分离关注点的目的。