当前位置:网站首页>LayaBox---TypeScript---装饰器
LayaBox---TypeScript---装饰器
2022-08-02 10:01:00 【格拉格拉】
目录
1.介绍
注意 装饰器是一项实验性特性,在未来的版本中可能会发生改变。
若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json
里启用experimentalDecorators
编译器选项:
1.1命令行
tsc --target ES5 --experimentalDecorators
//tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
2.装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。
装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
例如,有一个@sealed
装饰器,我们会这样定义sealed
函数:
function sealed(target) {
// do something with "target" ...
}
2.1装饰器工厂
如果我们要定制一个修饰器如何应用到一个声明上,我们得写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。
function color(value: string) { // 这是一个装饰器工厂
return function (target) { // 这是装饰器
// do something with "target" and "value"...
}
}
2.2装饰器组合
多个装饰器可以同时应用到一个声明上,就像下面的示例:
- 书写在同一行上:
@f @g x
- 书写在多行上:
@f @g x
当多个装饰器应用于一个声明上,它们求值方式与复合函数相似。在这个模型下,当复合f和g时,复合的结果(f ∘ g)(x)等同于f(g(x))。
同样的,在TypeScript里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:
- 由上至下依次对装饰器表达式求值。
- 求值的结果会被当作函数,由下至上依次调用。
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
在控制台里会打印出如下结果:
f(): evaluated
g(): evaluated
g(): called
f(): called
2.3装饰器求值
类中不同声明上的装饰器将按以下规定的顺序应用:
- 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
- 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
- 参数装饰器应用到构造函数。
- 类装饰器应用到类。
2.4类装饰器
类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中( .d.ts
),也不能用在任何外部上下文中(比如declare
的类)。
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
注意 如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中 不会为你做这些。
下面是使用类装饰器(@sealed
)的例子:
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
我们可以这样定义@sealed装饰器:
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
当@sealed
被执行的时候,它将密封此类的构造函数和原型。
下面是一个重载构造函数的例子:
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
}
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world"));
2.5方法装饰器
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts
),重载或者任何外部上下文(比如declare
的类)中。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意 如果代码输出目标版本小于
ES5
,属性描述符将会是undefined
。
如果方法装饰器返回一个值,它会被用作方法的属性描述符。
注意 如果代码输出目标版本小于
ES5
返回值会被忽略。
下面是一个方法装饰器(@enumerable
)的例子:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
我们可以用下面的函数声明来定义@enumerable装饰器:
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
这里的@enumerable(false)
是一个装饰器工厂。 当装饰器 @enumerable(false)
被调用时,它会修改属性描述符的enumerable
属性。
2.6访问器装饰器
访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare
的类)里。
注意 TypeScript不允许同时装饰一个成员的
get
和set
访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get
和set
访问器,而不是分开声明的。
访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意 如果代码输出目标版本小于
ES5
,Property Descriptor将会是undefined
。如果访问器装饰器返回一个值,它会被用作方法的属性描述符。
注意 如果代码输出目标版本小于
ES5
返回值会被忽略。
下面是使用了访问器装饰器(@configurable
)的例子:
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() { return this._x; }
@configurable(false)
get y() { return this._y; }
}
我们可以通过如下函数声明来定义@configurable装饰器:
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
2.7属性装饰器
属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare
的类)里。
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
注意 属性描述符不会做为参数传入属性装饰器,这与TypeScript是如何初始化属性装饰器的有关。 因为目前没有办法在定义一个原型对象的成员时描述一个实例属性,并且没办法监视或修改一个属性的初始化方法。返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性。
我们可以用它来记录这个属性的元数据,如下例所示:
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
然后定义@format
装饰器和getFormat
函数:
import "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("Hello, %s")
装饰器是个 装饰器工厂。 当 @format("Hello, %s")
被调用时,它添加一条这个属性的元数据,通过reflect-metadata
库里的Reflect.metadata
函数。 当 getFormat
被调用时,它读取格式的元数据。
注意 这个例子需要使用
reflect-metadata
库。 查看 元数据了解reflect-metadata
库更详细的信息。
2.8 参数装饰器
参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文(比如 declare
的类)里。
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引。
注意 参数装饰器只能用来监视一个方法的参数是否被传入。
参数装饰器的返回值会被忽略。
下例定义了参数装饰器(@required
)并应用于Greeter
类方法的一个参数:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@validate
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
然后我们使用下面的函数定义 @required 和 @validate 装饰器:
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
let method = descriptor.value;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
}
}
@required
装饰器添加了元数据实体把参数标记为必需的。 @validate
装饰器把greet
方法包裹在一个函数里在调用原先的函数前验证函数参数。
注意 这个例子使用了
reflect-metadata
库。
2.9元数据
一些例子使用了reflect-metadata
库。你可以通过npm安装这个库:
npm i reflect-metadata --save
ypeScript支持为带有装饰器的声明生成元数据。 你需要在命令行或 tsconfig.json
里启用emitDecoratorMetadata
编译器选项。
tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata
//tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
当启用后,只要reflect-metadata
库被引入了,设计阶段添加的类型信息可以在运行时使用。
示例:
import "reflect-metadata";
class Point {
x: number;
y: number;
}
class Line {
private _p0: Point;
private _p1: Point;
@validate
set p0(value: Point) { this._p0 = value; }
get p0() { return this._p0; }
@validate
set p1(value: Point) { this._p1 = value; }
get p1() { return this._p1; }
}
function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) {
let set = descriptor.set;
descriptor.set = function (value: T) {
let type = Reflect.getMetadata("design:type", target, propertyKey);
if (!(value instanceof type)) {
throw new TypeError("Invalid type.");
}
set(value);
}
}
TypeScript编译器可以通过@Reflect.metadata
装饰器注入设计阶段的类型信息。 你可以认为它相当于下面的TypeScript:
class Line {
private _p0: Point;
private _p1: Point;
@validate
@Reflect.metadata("design:type", Point)
set p0(value: Point) { this._p0 = value; }
get p0() { return this._p0; }
@validate
@Reflect.metadata("design:type", Point)
set p1(value: Point) { this._p1 = value; }
get p1() { return this._p1; }
}
注意 装饰器元数据是个实验性的特性并且可能在以后的版本中发生破坏性的改变(breaking changes)。
边栏推荐
- R语言ggplot2可视化:基于aes函数中的fill参数和shape参数自定义绘制分组折线图并添加数据点(散点)、使用theme函数的legend.position函数配置图例到图像右侧
- 李航《统计学习方法》笔记之k近邻法
- Use compilation to realize special effects of love
- 【技术分享】OSPFv3基本原理
- Supervised learning of Li Hang's "Statistical Learning Methods" Notes
- 超赞!发现一个APP逆向神器!
- Verilog的随机数系统任务----$random
- DirectX修复工具增强版「建议收藏」
- 从零开始Blazor Server(5)--权限验证
- HikariCP数据库连接池,太快了!
猜你喜欢
软件测试与质量 之白盒测试
You Only Hypothesize Once: 用旋转等变描述子估计变换做点云配准(已开源)
Verilog的随机数系统任务----$random
WPF 截图控件之文字(七)「仿微信」
MSYS2 QtCreator Clangd 代码分析找不到 mm_malloc.h的问题补救
牛客刷题——剑指offer(第三期)
瑞吉外卖项目剩余功能补充
【技术分享】OSPFv3基本原理
DVWA 通关记录 2 - 命令注入 Command Injection
In the whole development of chi V853 board tried to compile QT test
随机推荐
李航《统计学习方法》笔记之感知机perceptron
Long battery life or safer?Seal and dark blue SL03 comparison shopping guide
使用scrapy 把爬到的数据保存到mysql 防止重复
wireshark的安装教程(暖气片安装方法图解)
The perceptron perceptron of Li Hang's "Statistical Learning Methods" notes
Verilog's random number system task----$random
全新荣威RX5,27寸大屏吸引人,安全、舒适一个不落
李航《统计学习方法》笔记之监督学习Supervised learning
QT专题:组合会话框和文本编辑器
STL中list实现
Facebook's automated data analysis solution saves worry and effort in advertising
如何搭建威纶通触摸屏与S7-200smart之间无线PPI通信?
R语言使用ggpubr包的ggtexttable函数可视化表格数据(直接绘制表格图或者在图像中添加表格数据)、设置theme主题参数自定义表格中表头内容的填充色(使用colnames.style参数)
【技术分享】OSPFv3基本原理
剑指offer专项突击版第17天
程序员的浪漫七夕
Application scenarios of js anti-shake function and function throttling
List-based queuing and calling system
斯皮尔曼相关系数
The R language uses the rollapply function in the zoo package to apply the specified function to the time series in a rolling manner and the window moves, and set the align parameter to specify that t