上一篇文章中,咱们讨论了TypeScript源码中关于方法装饰器的实现,搞明白了以下几个问题:javascript
__decorate
函数干了些什么事情?接下来咱们继续属性装饰器的观察。java
属性装饰器的声明标识以下:segmentfault
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
属性描述符)app
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
为属性的名字)。this
接着声明了两个方法getter
和setter
,因为函数是闭包建立的,因此在其中能够访问变量_val
,在其中能够添加额外的自定义行为,这里添加了将属性值打印在控制台的操做。prototype
而后使用delete
操做符将原属性从类的原型中删除,不过须要注意的是:若是属性存在不可配置的属性时,这里if(delete this[key])
会返回false。而当属性被成功删除,方法Object.defineProperty()
将建立一个和原属性同名的属性,不一样的是新的属性getter
和setter
方法,使用上面新建立的。code
至此,属性装饰器的实现就完成了,运行结果以下:
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