Decorator装饰器

装饰器(Decorator)是一种与类(Class)相关的语法,现在typeScript中实现了Decorator。装饰器可以分为以下类型:

1.类的装饰

装饰器可以用来装饰整个类,当装饰器装饰类时,其实是在类上添加了静态属性。代码可见:

@testable
class MyTestableClass{
	//...
}
function testable(target){
	target.isTestable = true; // traget = MyTestableClass
}
console.log(MyTestableClass.isTestable) // true

为了更好的理解装饰器究竟是如何工作,我下载了typeScript并且开启了Decorator实验性功能,接着运行tsc命令,最终上述代码编译生成如下ES5代码

注意装饰器对类的行为的改变是在代码编译时发生的,因此使用tsc编译上述代码时会报错,这是因为tsc静态分析代码时无法在MyTestableClass中找到isTestable属性

var MyTestableClass = /** @class */ (function () {
    function MyTestableClass() {
    }
    MyTestableClass = __decorate([
        testable
    ], MyTestableClass);
    return MyTestableClass;
}());
function testable(target) {
    target.isTestable = true;
}

上述代码通过调用一个叫__decorate的函数来给MyTestableClass类进行装饰,···__decorate```代码如下:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) 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([testable], MyTestableClass);,此处只传入了两个参数,并且走的时arguments.length < 3的分支,于是从侧面证明装饰器除了可以装饰类,还可以装饰类属性

最终装饰类的效果如下:

class MyTestableClass
MyTestableClass = testable(MyTestableClass) || MyTestableClass

于是乎在阮一峰老师的ES6入门中所描述的扩展代码

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}

通过TypeScript编译之后最终效果类似于以下代码

class MyTestableClass
MyTestableClass = testable(true)(MyTestableClass) || MyTestableClass

2.方法的装饰

装饰器用来装饰方法的时候,装饰函数可以接受三个参数target, name, descriptor

使用以下代码进行typeScript编译:

function readonly(target, name, descriptor){
	descriptor.writable = false;
	return descriptor;
}

class Person{
	first:string;
	last:string;
	constructor(){
		this.first = 'Shon';
		this.last = 'sonw';
	}
	@readonly
	name(){return `${this.first} ${this.last}`}
}

最终得到如下的结果:

function readonly(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor;
}
var Person = /** @class */ (function () {
    function Person() {
        this.first = 'Shon';
        this.last = 'sonw';
    }
    Person.prototype.name = function () { return this.first + " " + this.last; };
    __decorate([
        readonly
    ], Person.prototype, "name", null);
    return Person;
}());

上述代码在使用__decorator装饰方法处理之后的效果相当于以下代码

readonly(Person.prototype, 'name', descriptor);//此处descriptor是name属性的特征值

* 多个装饰器的执行顺序

如果同一个方法有多个装饰器,实行顺序如下代码

function dec(id){
  console.log('evaluated', id);
  return (target, property, descriptor) => console.log('executed', id);
}

class Example {
    @dec(1)
    @dec(2)
    method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

经过TypeScript的编译可得到,

__decorate([
        dec(1),
        dec(2)
], Person.prototype, "method", null);

__deorate方法的调用代码如下

for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) :

__decorate方法调用时是现dec(1)然后dec(2),而在方法内部则是从decirators.length递减至0来执行装饰的,正是由于这种十分有特点的调用形式导致了如同剥洋葱一样的输出结果。

总结

通过装饰器可以很方便的实现如log日志,Mixin,Trait等功能,但是由于装饰器现在是ES7的提案,所以暂时没有浏览器支持装饰器,不过如果使用npm环境进行开发,则可以使用TypeScript将装饰器编译成ES5之后进行使用。

参考文档:
ECMAScript 6 入门(Decorate)TypeScript官方文档

行文仓促,如有错误,还望斧正 : )