装饰器与元数据反射(2)属与类性装饰器

38次阅读

共计 2932 个字符,预计需要花费 8 分钟才能阅读完成。

上一篇文章中,我们讨论了 TypeScript 源码中关于方法装饰器的实现,搞明白了如下几个问题:

装饰器函数是如何被调用的?
装饰器函数参数是如何传入的?

__decorate 函数干了些什么事情?

接下来我们继续属性装饰器的观察。
属性装饰器
属性装饰器的声明标识如下:
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
如下我们为一个类的属性添加了一个名为 @logProperty 的装饰器
class Person {

@logProperty
public name: string;
public surname: string;

constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
}
上一篇解释过,当这段代码最后被编译成 JavaScript 执行时,方法__decorate 会被调用,但此处会少最后一个参数(通过 Object. getOwnPropertyDescriptor 属性描述符)
var Person = (function () {
function Person(name, surname) {
this.name = name;
this.surname = surname;
}
__decorate(
[logProperty],
Person.prototype,
“name”
);
return Person;
})();
需要注意的是,这次 TypeScript 编译器并没像方法装饰器那样,使用__decorate 返回的结果覆盖原始属性。原因是属性装饰器并不需要返回什么。
Object.defineProperty(C.prototype, “foo”,
__decorate(
[log],
C.prototype,
“foo”,
Object.getOwnPropertyDescriptor(C.prototype, “foo”)
)
);
那么,接下来具体实现这个 @logProperty 装饰器
function logProperty(target: any, key: string) {

// 属性值
var _val = this[key];

// getter
var getter = function () {
console.log(`Get: ${key} => ${_val}`);
return _val;
};

// setter
var setter = function (newVal) {
console.log(`Set: ${key} => ${newVal}`);
_val = newVal;
};

// 删除属性
if (delete this[key]) {
// 创建新的属性
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
实现过程首先声明了一个变量_val,并用所装饰的属性值给它赋值(此处的 this 指向类的原型,key 为属性的名字)。
接着声明了两个方法 getter 和 setter,由于函数是闭包创建的,所以在其中可以访问变量_val,在其中可以添加额外的自定义行为,这里添加了将属性值打印在控制台的操作。
然后使用 delete 操作符将原属性从类的原型中删除,不过需要注意的是:如果属性存在不可配置的属性时,这里 if(delete this[key]) 会返回 false。而当属性被成功删除,方法 Object.defineProperty() 将创建一个和原属性同名的属性,不同的是新的属性 getter 和 setter 方法,使用上面新创建的。
至此,属性装饰器的实现就完成了,运行结果如下:
var me = new Person(“Remo”, “Jansen”);
// Set: name => Remo

me.name = “Remo H.”;
// Set: name => Remo H.

name;
// Get: name Remo H.
类装饰器
类装饰器的声明标识如下:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
可以像如下方式使用类装饰器:
@logClass
class Person {

public name: string;
public surname: string;

constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
}
和之前不同的是,经过 TypeScript 编译器编译为 JavaScript 后,调用__decorate 函数时,与方法装饰器相比少了后两个参数。仅传递了 Person 而非 Person.prototype。
var Person = (function () {
function Person(name, surname) {
this.name = name;
this.surname = surname;
}
Person = __decorate(
[logClass],
Person
);
return Person;
})();
值得注意的是,__decorate 的返回值复写了原始的构造函数,原因是类装饰器必须返回一个构造器函数。接下来我们就来实现上面用到的类装饰器 @logClass:
function logClass(target: any) {

// 保存对原始构造函数的引用
var original = target;

// 用来生成类实例的方法
function construct(constructor, args) {
var c : any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
return new c();
}

// 新的构造函数
var f : any = function (…args) {
console.log(“New: ” + original.name);
return construct(original, args);
}

// 复制原型以便 `intanceof` 操作符可以使用
f.prototype = original.prototype;

// 返回新的构造函数(会覆盖原有构造函数)
return f;
}
这里实现的构造器中,声明了一个名为 original 的变量,并将所装饰类的构造函数赋值给它。接着声明一个工具函数 construct,用来创建类的实例。然后定义新的构造函数 f,在其中调用原来的构造函数并将初始化的类名打印在控制台,当然我们也可以添加一些其他自定义的行为。
原始构造函数的原型被复制给 f 的原型,以确保在创建一个 Person 的新实例时,instanceof 操作符如愿以偿,具体原因可参考鄙人另一篇文章原型与对象。
至此类装饰器的实现就完成了,可以验证下:
var me = new Person(“Remo”, “Jansen”);
// New: Person

me instanceof Person;
// true

正文完
 0