本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-05-12
类装饰器是指接受一个类构造函数作为参数的函数,并且返回参数中提供的构造函数 或 一个新的构造函数。返回undefined等同于返回参数中提供的构造函数。如果有其他返回值,就会被用来覆盖类(指装饰器修饰的类)原来的构造函数。,比如 vue-class-component 中的 @component。
// 创建一个装饰器
function logClass(target: any) {
// todo ...
}
// 使用
@logClass
class Person {
public name: string;
public surname: string;
}
会被编译成
var Person = (function () {
function Person (name, surname) {
this.name = name;
this.surname = surname;
}
Person = _decorate([ // _decorate用来处理在运行时装饰类 _decorate
logClass
], Person);
return Person;
})();
写完类装饰器,实现功能。
// 修饰类,在类的实例被 构造 的时候。打印类的名字
function logClass(target: any) {
// 保存原构造函数的引用
var original = target;
// 用来生成类的实例的工具方法
// 继承的功能
// construct 返回 c的实例。 c的实例.__proto__ = constructor.prototype = target.prototype
function construct(constructor: any, args: any[]) {
var c: any = function() {
return constructor.apply(target, args);
}
c.prototype = constructor.prototype;
return new c();
}
// 新的 构造函数 行为
var f: any = function(...args: any[]) {
console.log('New:' + original.name);
return construct(original, args); // c的实例
}
// 复制原型,使instanceof 操作能正常使用
f.prototype = original.prototype;
return f;
}
装饰器用来为元素添加一些额外的逻辑或元数据,当我们想要拓展一个函数(类的方法或构造函数)的功能时,需要往原函数上包一个新函数,新函数里有额外的逻辑,且能执行原函数里的方法。
方法修饰器是一个接受三个参数的函数,包含这个属性的对象(target
)、属性名(key
一个字符串或一串符号)和一个可选参数(descriptor
属性的描述对象,自定义或者被处理为Object.getOwnPropertyDescriptor(target, key)
)。
如返回 undefined
等同于返回参数提供的属性描述对象;否则应提供一个新的属性描述对象(用来改变方法的行为),方法装饰器用来覆盖类的方法。 比如 vue-class-component 中的 @watch('varName', config)
。
// 为扩展类的一个函数 使用的时候 @logger1({ level: "info" })
function logger1(config?: { level: stirng }) {
// 这里返回的相当于是一个方法修饰器
return function(target, key: string, descriptor: PropertyDescriptor) {
const _value = descriptor.value; // 存储原函数
if (typeof _value === "function") {
// 创建一个新函数来替代被修饰的函数,新函数除了调用原函数外,还包含一些额外的逻辑。
descriptor.value = (...args) => {
config && console.log(`logger1-begin : ${config.level}`); // 原函数调用之前执行
const res = _value.apply(target, args); // 执行原函数
console.log(`logger1-end`); // 原函数执行之后执行
return res; // 返回原函数的调用结果
};
}
return descriptor;
};
}
属性装饰器函数是一个接受两个参数的函数,包含了这个属性的对象 和 这个属性的属性名,不会返回一个属性描述符对象。比如 vue-class-component 中的 @Prop()
。
function logProperty(target: any, key: string) {
// 属性值
var _val = target[key];
// 属性的getter
var getter = function () {
console.log(`Get:${key} => ${_val}`);
return _val;
}
// 属性的setter
var setter = function(newVal: string) {
console.log(`Set:${key} => ${newVal}`)
_val = newVal;
}
// 属性装饰器没有返回值,我们不能使用返回值来覆盖被修饰属性的值
// 所以手动删除了原属性,并使用 Object.defineProperty创建了新属性
if (delete target[key]) { // 成功删除返回true
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
class Person {
@logProperty
public name = 'jack';
}
参数装饰器函数是一个接受三个参数的函数,一个包含了被装饰参数的方法的对象、方法的名字(或undefined
)、和参数在参数列表中的索引,这个装饰器的返回值将会被忽略;
// 参数属性没有返回值,这意味着我们不能覆盖这个包含被装饰参数的方法
// 可以使用参数装饰器在 类的原型 上添加一些元数据
// 下列实现将会在类的原型上添加一个名为_log_${key}_parameters的数组
function addMetadata(target: any, key: string, index: number) {
var metaDataKey = `_log_${key}_parameters`;
(target[metaDataKey] || target[metaDataKey] = []).push(index)
}
// 单独的参数装饰器并不是很有用
// 它需要和方法装饰器结合,参数装饰器用来添加元数据,然后通过方式装饰器用来读取
function readMetadata(target: any, key: string, descriptor: any) {
var originalMethod = descriptor.value;
descriptor.value = (...args: any[]) => {
var metadataKey = `_log_${key}_parameters`;
var indices = target[metadataKey];
if (Array.isArray(indices)) {
for (var i = 0; i < args.length; i++) {
if (indices.indexOf(i) !== -1) { // 被参数修饰符修饰的那个参数
var arg = args[i];
var argStr = JSON.stringify(arg) || arg.toString();
console.log(`${key} arg[${i}]: ${argStr}`)
}
}
var result = originalMethod.apply(target, args); // 调用原方法
return result;
}
}
return descriptor;
}
class Person {
constructor(public name: string, public surname: string) {}
@readMetadata
saySomething(@addMetadata something: string) :string {
return `${this.name} ${this.surname} says: ${something}`;
}
}
var p = new Person('ma', 'jack');
p.saySomething('hello!');
// saySomething arg[0]: hello!
function ClassDecorator() {
return function (target: any) {
console.log("I am class decorator");
}
}
function MethodDecorator() {
return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
console.log("I am method decorator");
}
}
function Param1Decorator() {
return function (target: any, methodName: string, paramIndex: number) {
console.log("I am parameter1 decorator");
}
}
function Param2Decorator() {
return function (target: any, methodName: string, paramIndex: number) {
console.log("I am parameter2 decorator");
}
}
function PropertyDecorator() {
return function (target: any, propertyName: string) {
console.log("I am property decorator");
}
}
@ClassDecorator()
class Hello {
@PropertyDecorator()
greeting = '';
@MethodDecorator()
greet( @Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
}
// I am property decorator
// I am parameter2 decorator
// I am parameter1 decorator
// I am method decorator
// I am class decorator
属性装饰器和方法装饰器谁在前面谁先执行,而执行方法装饰器前,会先执行参数装饰器,最后执行类装饰器。
1、有多个参数装饰器时:从最后一个参数依次向前执行
2、方法和方法参数中参数装饰器先执行。
3、类装饰器总是最后执行。
4、方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行
为了给装饰器传递参数,需要使用一个函数来包裹装饰器,这个包裹接受参数并返回一个装饰器
@logClass({ level: 'info' })
class Person {
// ...
}
function logClass (option: any) {
return function (target: any) {
// ...
console.log(option)
}
}
/// <reference path="./node_modules/reflect-metadata/index.d.ts" />
import 'reflect-metadata';
class Demo {
@Reflect.metadata('name', 'jack')
public attr: string;
constructor(attr: string) {
this.attr = attr;
}
}
var d = new Demo('test');
var a = Reflect.getMetadata('name', d);
console.log(a); // undefined
var a = Reflect.getMetadata('name', d, 'attr');
console.log(a); // jack
// 在对象中反射 而不是在类中反射
var a = Reflect.getMetadata('name', Demo);
console.log(a); // undefined
var a = Reflect.getMetadata('name', Demo, 'attr');
console.log(a); // undefined
class Person {
@logProperty
public name = 'jack';
}
会被编译成
var Person = /** @class */ (function () {
function Person() {
this.name = 'jack';
}
__decorate([
logProperty
], Person.prototype, "name", void 0);
return Person;
}());
其中,__decorate 函数的定义如下
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length,
// r的第一次取值
r = c < 3 ?
target :
desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
d; // 备用变量
// 如果存在 Reflect.decorate, 则将 r的取值交给 Reflect.decorate 去处理。
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
r = Reflect.decorate(decorators, target, key, desc);
} else {
// 将 decorators “装饰器们” 进行循环处理
for (var i = decorators.length - 1; i >= 0; i--) {
if (d = decorators[i]) {// 将每个 decorator 赋值给 d
// r的第二次取值
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;
};
关于类装饰器、属性装饰器、方法装饰器、参数装饰器的差别
__decorate([
PropertyDecorator()
], Hello.prototype, "greeting", void 0);
__decorate([
MethodDecorator(),
__param(0, Param1Decorator()), __param(1, Param2Decorator())
], Hello.prototype, "greet", null);
Hello = __decorate([
ClassDecorator()
], Hello);;
可知 当为属性装饰器或方法装饰器(参数装饰器需要和方法装饰器一起使用) 参数长度为 4。只有为类装饰器的时候 参数长度为 2。
分析:
1、最开始r的取值:为了取得真正应该操作的“对象”,是操作源对象 或 源对象的属性/方法。
2、最后r的取值
d被赋值为 装饰器的实现函数。
2.1、d 处理的时候,就会执行那些装饰器的相关操作。
2.2、d 的返回值,当装饰器 d 处理完之后,如果这个装饰器有返回值就使用返回值,否则还是返回 r。即第一步取到的r,即不变。
3、如果是 方法和属性装饰器,还需要为源对象的属性或方法重新定义操作过后的 Descriptor 对象,此时对应的属性或方法已被重写,最终返回处理过的 r。