TypeScript躬行记(8)——装饰器
装饰器(Decorator)可声明在类及其成员(例如属性、方法等)之上,为它们提供一种标注,用于分离复杂逻辑或附加额外逻辑,其语法形式为@expression。expression是一个会在运行时被调用的函数,它的参数是被装饰的声明信息。假设有一个@sealed装饰器,那么可以像下面这样定义sealed()函数。
function sealed(target) {
//...
}
有两种方式可以开启装饰器,第一种是在输入命令时添加--experimentalDecorators参数,如下所示,其中--target参数不能省略,它的值为“ES5”。
tsc default.ts --target ES5 --experimentalDecorators
第二种是在tsconfig.json配置文件中添加experimentalDecorators属性,如下所示,对应的target属性也不能省略。
{
compilerOptions: {
target: "ES5",
experimentalDecorators: true
}
}
一、类装饰器
类装饰器用于监听、修改或替换类的构造函数,并将其作为类装饰器唯一可接收的参数。当装饰器返回undefined时,延用原来的构造函数;而当装饰器有返回值时,会用它来覆盖原来的构造函数。下面的示例会通过类装饰器封闭类的构造函数和原型,其中@sealed声明在类之前。
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
在经过TypeScript编译后,将会生成一个__decorated()函数,并应用到Person类上,如下所示。
var Person = /** @class */ (function() {
function Person(name) {
this.name = name;
}
Person = __decorate([sealed], Person);
return Person;
})();
注意,类装饰器不能出现在.d.ts声明文件和外部类之中。
二、方法装饰器
方法装饰器声明在类的方法之前,作用于方法的属性描述符,比类装饰器还多一个重载限制。它能接收三个参数,如下所列:
(1)对于静态成员来说是类的构造函数,而对于实例成员则是类的原型对象。
(2)成员的名字,一个字符串或符号。
(3)成员的属性描述符,当输出版本低于ES5时,该值将会是undefined。
当方法装饰器返回一个值时,会覆盖当前方法的属性描述符。下面是一个简单的例子,方法装饰器的第一个参数是Person.prototype,第二个是“cover”,调用getName()方法得到的将是“freedom”,而不是原先的“strick”。
class Person {
@cover
getName(name) {
return name;
}
}
function cover(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function() {
return "freedom";
};
return descriptor;
}
let person = new Person();
person.getName("strick"); //"freedom"
三、访问器装饰器
访问器装饰器声明在类的访问器属性之前,作用于相应的属性描述符,其限制与类装饰器相同,而接收的三个参数与方法装饰器相同。并且还需要注意一点,TypeScript不允许同时装饰一个成员的get和set访问器,只能应用在第一个访问器上。
以下面的Person类为例,定义了一个访问器属性name,当访问它时,得到的将是“freedom”,而不是原先的“strick”。
class Person {
private _name: string;
@access
get name() {
return this._name;
}
set name(name) {
this._name = name;
}
}
function access(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.get = function() {
return "freedom";
};
return descriptor;
}
let person = new Person();
person.name = "strick";
console.log(person.name); //"freedom"
四、属性装饰器
属性装饰器声明在属性之前,其限制与访问器装饰器相同,但只能接收两个参数,不存在第三个属性描述符参数,并且没有返回值。仍然以下面的Person类为例,定义一个name属性,并且在@property装饰器中修改其值。
class Person {
@property
name: string;
}
function property(target: any, key: string) {
Object.defineProperty(target, key, {
value: "freedom"
});
}
let person = new Person();
person.name = "strick";
console.log(person.name); //"freedom"
五、参数装饰器
参数装饰器声明在参数之前,它没有返回值,其限制与方法装饰器相同,并且也能接收三个参数,但第三个参数表示装饰的参数在函数的参数列表中所处的位置(即索引)。下面用一个例子来演示参数装饰器的用法,需要与方法装饰器配合。
let params = [];
class Person {
@func
getName(@required name) {
return name;
}
}
在@func中调用getName()方法,并向其传入params数组中的值,@required用于修改指定位置的参数的值,如下所示。
function func(target: any, key: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function () {
return method.apply(this, params);
};
return descriptor;
}
function required(target: any, key: string, index: number) {
params[index] = "freedom";
}
当实例化Person类,调用getName()方法,得到的将是“freedom”。
let person = new Person();
person.getName("strick"); //"freedom"
六、装饰器工厂
装饰器工厂是一个能接收任意个参数的函数,用来包裹装饰器,使其更易使用,它能返回上述任意一种装饰器函数。接下来改造方法装饰器一节中的cover()函数,接收一个字符串类型的value参数,返回一个方法装饰器函数,如下所示。
function cover(value: string) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function() {
return value;
};
return descriptor;
};
}
在将@cover作用于类中的方法时,需要传入一个字符串,如下所示。
class Person {
@cover("freedom")
getName(name) {
return name;
}
}
七、装饰器组合
将多个装饰器应用到同一个声明上时,既可以写成一行,也可以写成多行,如下所示。
/****** 一行 ******/
@first @second desc
/****** 多行 ******/
@first
@second
desc
这些装饰器的求值方式与复合函数类似,先由上至下依次执行装饰器,再将求值结果作为函数,由下至上依次调用。例如定义两个装饰器工厂函数,如下代码所示,在函数体和返回的装饰器中都会打印一个数字。
function first() {
console.log(1);
return function(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(2);
};
}
function second() {
console.log(3);
return function(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(4);
};
}
将它们先后声明到类中的同一个方法,如下代码所示。根据求值顺序可知,先打印出1和3,再打印出4和2。
class Person {
@first()
@second()
getName(name) {
return name;
}
}
TypeScript躬行记(8)——装饰器的更多相关文章
- TypeScript躬行记(2)——接口
在传统的面向对象语言中,接口(Interface)好比协议,它会列出一系列的规则(即对行为进行抽象),再由类来实现这些规则.而TypeScript中的接口更加灵活,除了包含常规的作用之外,它还能扩展其 ...
- TypeScript躬行记(1)——数据类型
TypeScript不仅支持JavaScript所包含的数据类型,还额外扩展了许多实用的数据类型,例如枚举.空值.任意值等. 一.JavaScript的数据类型 JavaScript的数据类型包括6种 ...
- TypeScript躬行记(3)——类
类是对对象的抽象,描述了对象的特征和行为,而对象就是类的实例.ES6引入了类的概念(相关内容可参考ES类和ES6类的继承两节),TypeScript在此基础上,不仅根据ES7等规范完善了类的语法,还添 ...
- TypeScript躬行记(5)——类型兼容性
TypeScript是一种基于结构类型的语言,可根据其成员来描述类型.以结构相同的Person接口和Programmer类为例,如下所示. interface Person { name: strin ...
- TypeScript躬行记(7)——命名空间
TypeScript中的命名空间可将那些具有内在联系的接口.类或对象等代码组织在一起,既能隔离作用域,也能避免命名冲突,并且使得代码结构清晰,更易追踪.在命名空间内部,所有实体部分默认都是私有的,需要 ...
- TypeScript躬行记(6)——高级类型
本节将对TypeScript中类型的高级特性做详细讲解,包括交叉类型.类型别名.类型保护等. 一.交叉类型 交叉类型(Intersection Type)是将多个类型通过“&”符号合并成一个新 ...
- TypeScript躬行记(4)——泛型
泛型是程序设计语言中的一种风格或范式,相当于类型模板,允许在声明类.接口或函数等成员时忽略类型,而在未来使用时再指定类型,其主要目的是为它们提供有意义的约束,提升代码的可重用性. 一.泛型参数 当一个 ...
- ES6躬行记(1)——let和const
古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...
- ES6躬行记 笔记
ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向
随机推荐
- SDUT-2120_数据结构实验之链表五:单链表的拆分
数据结构实验之链表五:单链表的拆分 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 输入N个整数顺序建立一个单链表,将该 ...
- javax.websocket.Session的一个close异常记录
一刷新页面就报错如下: Connection closed 四月 10, 2018 11:20:18 上午 org.apache.tomcat.websocket.pojo.PojoEndpointB ...
- Libev源码分析05:Libev中的绝对时间定时器
Libev中的超时监视器ev_periodic,是绝对时间定时器,不同于ev_timer,它是基于日历时间的.比如如果指定一个ev_periodic在10秒之后触发(ev_now() + 10),然后 ...
- Android Xutils框架HttpUtil Get请求缓存问题
话说,今天和服务器开发人员小小的逗逼了一下,为啥呢? 话说今天有个"收藏产品"的请求接口,是get request的哦,我客户端写好接口后,点击"收藏按钮",返 ...
- 解决margin塌陷和margin合并
<!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...
- js毫秒数转天时分秒
formatDuring: function(mss) { var days = parseInt(mss / (1000 * 60 * 60 * 24)); var hours = pars ...
- Google Colab使用教程
简介Google Colaboratory是谷歌开放的云服务平台,提供免费的CPU.GPU和TPU服务器. 目前深度学习在图像和文本上的应用越来越多,不断有新的模型.新的算法获得更好的效果,然而,一方 ...
- H3C ISDN BRI和PRI
- java List接口中常用类
Vector:线程安全,但速度慢,已被ArrayList替代. ArrayList:线程不安全,查询速度快. LinkedList:链表结构,增删速度快.取出List集合中元素的方式: get(int ...
- Python--day38---进程间通信--初识队列(multiprocess.Queue)之生产者,消费者模型
1,生产者消费者模型.py import random import time from multiprocessing import Queue, Process def producer(name ...