分享一下学习装饰器(
Decorators
)的所得express
装饰器是TypeScript
提供的最强大的功能之一,它使咱们可以以干净的声明性方式扩展类和方法的功能。装饰器目前是JavaScript
的第2阶段提议,但在TypeScript
生态系统中已受到欢迎,主要的开放源代码项目(例如Angular
)正在使用装饰器。json
本人工做中是使用Angular8进行项目一个项目的开发的,接触装饰器这一方面也就比较多,因此就趁着周末整理了一篇关于装饰器Decorators
的文章,也但愿能帮助到学习这方面的同窗们。废话很少说,下面我们进入正题。c#
Decorators
首先咱们要在tsconfig.json
里面启用experimentalDecorators编译器选项:api
命令行:tsc --target ES5 --experimentalDecorators
数组
tsconfig.json:bash
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
复制代码
咱们先明确两个概念:app
@expression
的形式实际上是一个语法糖,expression
求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。JavaScript
中的Class
其实也是一个语法糖。例如咱们在Javascript
中声明一个Class
:ide
Class Animal {
eat() {
console.log('eat food')
}
}
复制代码
上面这个Animal类实际等同于下面这样:函数
function Animal() {}
Object.defineProperty(Animal.prototype, 'eat', {
value: function() { console.log('eat food'); },
enumerable: false,
configurable: true,
writable: true
});
复制代码
类装饰器应用于类的构造函数,可用于观察、修改或替换类定义。学习
function setDefaultDesc(constructor: Function){
constructor.prototype.desc = '类装饰器属性'
}
@setDefaultDesc
class Animal {
name: string;
desc: string;
constructor() {
this.name = 'dog';
}
}
let animal= new Animal();
console.log(animal.desc) // '类装饰器属性'
复制代码
下面是使用重载函数的例子。
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
return class extends constructor {
newProperty = "new property";
desc = "override";
}
}
@classDecorator
class Animal {
property = "property";
desc: string;
constructor(m: string) {
this.desc = m;
}
}
console.log(new Animal("world")); // Animal: {property: "property", desc: "override", newProperty: "new property" }
复制代码
这部分代码的含义是:被classDecorator
装饰的类里面若是不存在newProperty
或desc
属性,会增长相应的属性和对应的value
,若是存在该属性就会重写该属性的value
。
方法装饰器声明在一个方法的声明以前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,能够用来监视,修改或者替换方法定义。
方法装饰器表达式会在运行时看成函数被调用,传入下列3个参数:
Employee.prototype
)Object.getOwnPropertyDescriptor(Employee.prototype, propertyKey)
function logMethod(
target: Object,
propertyName: string,
propertyDesciptor: PropertyDescriptor): PropertyDescriptor {
// target === Employee.prototype
// propertyName === "greet"
// propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet")
const method = propertyDesciptor.value;
propertyDesciptor.value = function (...args: any[]) {
// 将参数列表转换为字符串
const params = args.map(a => JSON.stringify(a)).join();
// 调用该方法并让它返回结果
const result = method.apply(this, args);
// 转换结果为字符串
const r = JSON.stringify(result);
// 在控制台中显示函数调用细节
console.log(`Call: ${propertyName}(${params}) => ${r}`);
// 返回调用的结果
return result;
}
return propertyDesciptor;
};
class Employee {
constructor(
private firstName: string,
private lastName: string
) {
}
@logMethod
greet(message: string): string {
return `${this.firstName} ${this.lastName} : ${message}`;
}
}
const emp = new Employee('三月风情', '陌上花开');
emp.greet('三月风情陌上花'); // return: '三月风情 陌上花开 : 三月风情陌上花'
复制代码
访问器只是类声明中属性的getter和setter部分。 访问器装饰器是在访问器声明以前声明的。访问器装饰器应用于访问器的属性描述符,可用于观察、修改或替换访问器的定义。
访问器装饰器表达式会在运行时看成函数被调用,传入下列3个参数:
Employee.prototype
)Object.getOwnPropertyDescriptor(Employee.prototype, propertyKey)
下面是使用了访问器装饰器(@configurable
)的例子,应用于Point
类的成员上:
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
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; }
}
复制代码
属性装饰器函数须要两个参数:
function logParameter(target: Object, propertyName: string) {
// 属性的值
let _val = target[propertyName];
// 属性的get方法
const getter = () => {
console.log(`Get: ${propertyName} => ${_val}`);
return _val;
};
// 属性的set方法
const setter = newVal => {
console.log(`Set: ${propertyName} => ${newVal}`);
_val = newVal;
};
// 删除属性.
if (delete target[propertyName]) {
// 使用getter和setter建立新属性
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
class Employee {
@logParameter
name: string;
}
const emp = new Employee();
emp.name = '陌上花开'; // Set: name => 陌上花开
console.log(emp.name);
// Get: name => 陌上花开
// 陌上花开
复制代码
参数装饰函数须要三个参数:
function logParameter(target: Object, propertyName: string, index: number) {
// 为相应的方法生成元数据
// 保持被修饰参数的位置
const metadataKey = `log_${propertyName}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
class Employee {
greet(@logParameter message: string): string {
return `hello ${message}`;
}
}
const emp = new Employee();
emp.greet('hello');
复制代码
在上面的代码示例中:target
为Employee的实例emp
,propertyName
的值为greet
,index
的值为0
。
咱们先假设这样一个场景,好比咱们须要几个装饰器,分别把一个类中的部分属性、类自己、方法、参数的名称打印出来,这时候咱们该怎么作。
import { logClass } from './class-decorator';
import { logMethod } from './method-decorator';
import { logProperty } from './property-decorator';
import { logParameter } from './parameter-decorator';
// 假设咱们已经有了上面这些装饰器,下面咱们就该这样作。
function log(...args) {
switch (args.length) {
case 3:
// 能够是方法装饰器仍是参数装饰器
if (typeof args[2] === "number") {
// 若是第三个参数是number那么它的索引就是它的参数装饰器
return logParameter.apply(this, args);
}
return logMethod.apply(this, args);
case 2:
// 属性装饰器
return logProperty.apply(this, args);
case 1:
// 类装饰器
return logClass.apply(this, args);
default:
// length长度在1,2,3外的状况
throw new Error('Not a valid decorator');
}
}
@log
class Employee {
@log
private name: string;
constructor(name: string) {
this.name = name;
}
@log
greet(@log message: string): string {
return `${this.name} says: ${message}`;
}
}
复制代码
装饰器工厂就是一个简单的函数,它返回一种类型的装饰器。
@Reflect.metadata('name', 'A')
class A {
@Reflect.metadata('hello', 'world')
public hello(): string {
return 'hello world'
}
}
Reflect.getMetadata('name', A) // 'A'
Reflect.getMetadata('hello', new A()) // 'world'
复制代码
反射, ES6+ 加入的 Relfect 就是用于反射操做的,它容许运行中的 程序对自身进行检查,或者说“自审”,并能直接操做程序的内部属性和方法,反射这个概念其实在 Java/c# 等众多语言中已经普遍运用了
再来一个小例子来看下:
function logParameter(target: Object, propertyName: string, index: number) {
// 从目标对象获取元数据
const indices = Reflect.getMetadata(`log_${propertyName}_parameters`, target, propertyName) || [];
indices.push(index);
// 将元数据定义为目标对象
Reflect.defineMetadata(`log_${propertyName}_parameters`, indices, target, propertyName);
}
// 属性装饰器使用反射api来获取属性的运行时类型
export function logProperty(target: Object, propertyName: string): void {
// 从对象中获取属性的设计类型
var t = Reflect.getMetadata("design:type", target, propertyName);
console.log(`${propertyName} type: ${t.name}`); // name type: String
}
class Employee {
@logProperty
private name: string;
constructor(name: string) {
this.name = name;
}
greet(@logParameter message: string): string {
return `${this.name} says: ${message}`;
}
}
复制代码
在上面的例子中,咱们使用了反射元数据设计键[design:type]。目前只有三种:
design:type
design:paramtypes
design:returntype
这篇文章主要介绍了类装饰器,方法装饰器,访问器装饰器,属性装饰器,参数装饰器,装饰器工厂,和元数据Reflection。也是我学习过程当中的一些总结。每周都会持续更新不一样的技术,喜欢的同窗能够点赞加关注,你们一块儿进步。若是有想学习某方面技术的同窗也欢迎评论区留言,我会努力写出你们感兴趣的内容。