当前位置 博文首页 > jcLee95的博客:TypeScript笔记(15)—— 深入理解TypeScript中
CSDN:jcLee95
邮箱:291148484@163.com
专题目录:https://blog.csdn.net/qq_28550263/article/details/115601920
如果你对TypeScript装饰器
有所疑惑,那么欢迎阅读本文。希望对你有所帮助,感谢支持!
【导读】:和其它很多功能一样,装饰器也不是TypeScript独有。实际上装饰器是一种用于拓展、修改对象行为的一种软件涉及模式。这种模式提供了我们不用知道原对象就可以对其进行对其更改的特点。
@decorator_name
的形式写在类声明之前。定义类装饰器的基本格式为:
function classDec1(constructor) {
}
使用类装饰器的格式为将@装饰器名
紧写在被装饰的类上,后面其它装饰器多数类似(除参数装饰器写在形参前)。即装饰器一般这样应用:
@decorator_name
class{
...
}
示例1. 多个类装饰器装饰于同一个类的执行顺序:
function classDec1(constructor: Function) {
/**
* 类装饰器1:对类装饰类的constructor做一些事情
* @param {constructor}:Function 被装饰类的构造函数作为其唯一参数
*/
console.log('执行类装饰器`classDec1`的语句');
console.log('类装饰器语句执行:可在构造类前对类构造函数进行包装');
}
function classDec2(constructor: Function) {
/**
* 类装饰器2:对类装饰类的constructor做一些事情
* @param {constructor}:Function 被装饰类的构造函数作为其唯一参数
*/
console.log('执行类装饰器`classDec2`的语句');
}
@classDec1
@classDec2
class MyClass {
attrib: string;
constructor(attrib: string) {
console.log('原构造函数执行');
this.attrib = attrib;
}
}
let cls = new MyClass("attrib value")
Out[]:
执行类装饰器`classDec2`的语句
执行类装饰器`classDec1`的语句
类装饰器语句执行:可在构造类前对类构造函数进行包装
原构造函数执行
示例2. 使用类装饰器对类扩展
定义一个类装饰器
// 如何 扩展/装饰 一个类
function classDec<T extends {new(...args:any[]):{}}> (constructor:T) {
/**
* 接收被装饰类的构造函数`constructor`作为唯一参数
* 剩余参数 `...args` 接收原被装饰类构造器的参数
*/
return class extends constructor {
/* 对被装饰的类进行扩展 */
newProperty1 = "new property 1"; // 被扩展的新属性1
newProperty2 = "new property 2"; // 被扩展的新属性2
}
}
其中
...args
是一个数组参数(...args:any[])
被装饰类构造时的参数列表。{new(...args:any[]):{}}
是所谓mixin 构造类型,它是有一个构造签名、带有一个类型为any[]的剩余参数(用…表示的数组型参数)和一个对象(object-like)的 返回类型。比如给定一个对象类型X
,那么new (...args: any[]) => X
是一个带有实例类型X
的mixin 构造器类型
。mixin类是扩展类型参数类型表达式的类声明或表达式。将装饰器应用到类上:
@classDec // 将写好的装饰器应用到某个类上,如法格式为 @expression
class MyClass1 {
property1 = "property 1";
property2: string;
constructor(property2: string) {
this.property2 = property2;
}
}
@classDec // 就像被声明的函数一样,同一个装饰器也能反复装饰不同的类
class MyClass2 {
constructor() {
/**这个类在被装饰前啥也没有 */
}
}
console.log(new MyClass1("property2 value"));
console.log(new MyClass2());
Out[]:
MyClass1 {
property1: 'property 1',
property2: 'property2 value',
newProperty1: 'new property 1',
newProperty2: 'new property 2'
}
MyClass2 {
newProperty1: 'new property 1',
newProperty2: 'new property 2'
}
示例3. 在装饰器中提前创建类的实例该实例:
虽然这个例子比较无聊,但是还是有助于对类装饰器用法的理解的。
let father = '小明父亲'
let mother = '小明母亲'
let child = '小明'
// 定义一个装饰器
function assign(constructor:any){
let a = Object.assign(constructor) // 通过被装饰类的构造器拷贝该类
new a(mother,child)
}
@assign
class MyClass {
constructor(parent: string, child: string) {
console.log(parent+'属于'+child+'的parents。')
}
}
new MyClass(father,child)
Out[]:
小明母亲属于小明的parents。
小明父亲属于小明的parents。
示例4. 使用装饰器让类封闭。
// 定义一个装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
}
@sealed
class MyClass {
prop1 :string
prop2: string
constructor(prop1,prop2) {
this.prop1 = prop1;
this.prop2 = prop2;
}
}
console.log('MyClass是否是封闭的:'+Object.isSealed(MyClass));
Object.defineProperty(MyClass,'prop1',{
enumerable:true
})
for(let i in MyClass){
console.log(i);
}
当把@sealed
注释掉时,运行的结果为:
MyClass是否是封闭的:false
prop1
可以看到在没有使用该装饰器时我们可以为MyClass
类(也是对象)的属性使用属性描述符enumerable
使之可遍历。然而使用@sealed
装饰器后,语句Object.seal(constructor)
通过作用类的构造函数装饰类,实际上就是使得类对应的对象封闭。这时如果再对其属性进行描述(参考JavaScript属性描述符相关内容)就会报错:
通过这样一个装饰器函数,我们只要将其装饰到任意需要封闭的类上,再不改变或重写该类就可以直接将其封闭了。从以上的例子我们看出:
@expression
,其中的expression
求值后就是装饰器的函数名;@expression
本质上看就是装饰器函数的调用。这里必须指出类本身就是一个对象,typescript使用并扩展ES6类语法糖。实际上在ES6后也不存在基于类的面向对象编程语言中的类,ES的以后类仍然是基于原型链的,而这些类不过是函数模拟的。底层使用装饰器装饰该类前后的JavaScript代码分别为:
使用装饰器前对应的JavaScript代码
var MyClass = /** @class */ (function () {
function MyClass(prop1, prop2) {
this.prop1 = prop1;
this.prop2 = prop2;
}
return MyClass;
}());
使用装饰器后对应的JavaScript代码
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
// 装饰器函数
function sealed(constructor) {
Object.seal(constructor);
}
// 被装饰器装饰的类 MyClass 在底层也是函数
var MyClass = /** @class */ (function () {
function MyClass(prop1, prop2) {
this.prop1 = prop1;
this.prop2 = prop2;
}
MyClass = __decorate(
[sealed], // 所有通过`@expression`等表达式装收到类的装饰器依顺序在此数组中
MyClass
);
return MyClass;
}());
属性描述符
上;Typescript中也可以定义类方法的装饰器,其语法格式为:
function methodDecorator(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
/**
* 方法装饰器:
* 类型:MethodDecorator
* @param {target} 对于实例成员是类的`原型对象`,对于静态成员来说是类的`构造函数 constructor`
* @param {propertyKey} 成员的名字
* @param {descriptor} 成员的属性描述符( PropertyDescriptor )
*/
};
上面说到,如果方法装饰器返回一个值,它会被用作方法的属性描述符(如果代码输出目标版本小于ES5返回值会被忽略),这里简单回顾一下 属性描述符 :
PropertyDescriptor
(属性描述符)用于声明一个属性是否可以写入到是否可以删除可以枚举和指定内容的对象,它是可以具有以下键值的对象: