装饰器简介
装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。
需要注意的是:装饰器是一项实验性特性,在未来的版本中可能会发生改变。
若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json
里启用experimentalDecorators
编译器选项:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
如何定义
装饰器使用@expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
装饰器组合
多个装饰器可以同时应用到一个声明上,就像下面的示例:
书写在同一行上:
@f @g x
书写在多行上:
@f
@g
x
在TypeScript里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:
- 由上至下依次对装饰器表达式求值。
- 求值的结果会被当作函数,由下至上依次调用。
类装饰器
类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中(比如declare的类)。
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
我们来看一个例子:
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
} @sealed
class MyClass {
a: number = 0;
b: string = "hello";
} var obj = new MyClass();
// obj.c = true; // 编译报错
通过类装饰器我们可以对类的原型对象做一定的修改。
编译后的源码:
"use strict";
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;
};
function sealed(constructor) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
var MyClass = (function () {
function MyClass() {
this.a = 0;
this.b = "hello";
}
MyClass = __decorate([
sealed
], MyClass);
return MyClass;
}());
var obj = new MyClass();
// obj.c = true; // 编译报错
这里我们只关注传递到装饰函数的constructor参数:就是我们定义的MyClass对象。
方法装饰器
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意:如果代码输出目标版本小于ES5,属性描述符将会是undefined。
我们来看下面的例子:
function methodDecorator(param1: boolean, param2?: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor));
};
} class MyClass {
@methodDecorator(true, "this is static")
public static sFunc(): void {
console.log("call static method");
} @methodDecorator(false)
public func(): void {
console.log("call method");
}
} MyClass.sFunc();
MyClass.sFunc(); var obj = new MyClass();
obj.func();
obj.func();
输出如下:
false, undefined, [object Object], func, {"writable":true,"enumerable":true,"configurable":true}
true, this is static, function MyClass() {
}, sFunc, {"writable":true,"enumerable":true,"configurable":true}
call static method
call static method
call method
call method
我们可以发现,方法装饰器返回的函数会在解释类的对应方法时被调用一次,并可以得到装饰器的参数和被装饰的方法的相关信息。
装饰器方法的调用只会在加载代码时执行一次,调用被装饰的方法不会触发装饰器方法。
编译后的源码:
"use strict";
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;
};
function methodDecorator(param1, param2) {
return function (target, propertyKey, descriptor) {
console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor));
};
}
var MyClass = (function () {
function MyClass() {
}
MyClass.sFunc = function () {
console.log("call static method");
};
MyClass.prototype.func = function () {
console.log("call method");
};
__decorate([
methodDecorator(false)
], MyClass.prototype, "func", null);
__decorate([
methodDecorator(true, "this is static")
], MyClass, "sFunc", null);
return MyClass;
}());
MyClass.sFunc();
MyClass.sFunc();
var obj = new MyClass();
obj.func();
obj.func();
这里我们只关注传递到装饰函数的target参数:
装饰静态方法时是定义的MyClass对象;
装饰动态方法时是定义的MyClass对象的原型对象,在24行这里“], MyClass.prototype, "func", null);”,我们知道,MyClass的原型对象在没有继承时是一个Object对象,该对象是所有MyClass的实例共用的,在有继承时,MyClass的原型对象会是一个新创建的对象,并且会对父类的方法进行浅复制的对象,如下:
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// ...
var MyClass = (function () {
function MyClass() {
}
// ...
return MyClass;
}());
var MyClass2 = (function (_super) {
__extends(MyClass2, _super);
function MyClass2() {
return _super !== null && _super.apply(this, arguments) || this;
}
// ...
return MyClass2;
}(MyClass));
此时如果操作target对象,比如为该对象添加一个属性,那么这个属性MyClass类的所有实例都可以访问到。
访问器装饰器
访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。
访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意:如果代码输出目标版本小于ES5,Property Descriptor将会是undefined。
同时TypeScript不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。
我们来看一个例子:
function methodDecorator(param1: boolean, param2?: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor));
};
} class MyClass {
private static _myName: string; @methodDecorator(true, "this is static")
public static set myName(value: string) {
this._myName = value;
} public static get myName(): string {
return this._myName;
} private _age: number; @methodDecorator(false)
public set age(value: number) {
this._age = value;
} public get age(): number {
return this._age;
}
} MyClass.myName = "hello";
console.log(MyClass.myName); var obj = new MyClass();
obj.age = 28;
console.log(obj.age);
输出如下:
false, undefined, [object Object], age, {"enumerable":true,"configurable":true}
true, this is static, function MyClass() {
}, myName, {"enumerable":true,"configurable":true}
hello
28
我们可以发现,访问器装饰器返回的函数会在解释类的对应访问器时被调用一次,并可以得到装饰器的参数和被装饰的访问器的相关信息。
装饰器方法的调用只会在加载代码时执行一次,调用被装饰的访问器不会触发装饰器方法。
编译后的源码:
"use strict";
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;
};
function methodDecorator(param1, param2) {
return function (target, propertyKey, descriptor) {
console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor));
};
}
var MyClass = (function () {
function MyClass() {
}
Object.defineProperty(MyClass, "myName", {
get: function () {
return this._myName;
},
set: function (value) {
this._myName = value;
},
enumerable: true,
configurable: true
});
Object.defineProperty(MyClass.prototype, "age", {
get: function () {
return this._age;
},
set: function (value) {
this._age = value;
},
enumerable: true,
configurable: true
});
__decorate([
methodDecorator(false)
], MyClass.prototype, "age", null);
__decorate([
methodDecorator(true, "this is static")
], MyClass, "myName", null);
return MyClass;
}());
MyClass.myName = "hello";
console.log(MyClass.myName);
var obj = new MyClass();
obj.age = 28;
console.log(obj.age);
请参考方法装饰器。
属性装饰器
属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
我们来看一个例子:
function propDecorator(param1: boolean, param2?: string) {
return function (target: any, propertyKey: string) {
console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey);
};
} class MyClass {
@propDecorator(false, "Hi")
public static A: number = 0; @propDecorator(true)
public a: string = "hello";
} console.log(MyClass.A);
var obj = new MyClass();
console.log(obj.a);
输出如下:
true, undefined, [object Object], a
false, Hi, function MyClass() {
this.a = "hello";
}, A
0
hello
我们可以发现,属性装饰器返回的函数会在解释类的对应属性时被调用一次,并可以得到装饰器的参数和被装饰的属性的相关信息。
装饰器方法的调用只会在加载代码时执行一次,调用被装饰的属性不会触发装饰器方法。
编译后的源码:
"use strict";
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;
};
function propDecorator(param1, param2) {
return function (target, propertyKey) {
console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey);
};
}
var MyClass = (function () {
function MyClass() {
this.a = "hello";
}
MyClass.A = 0;
__decorate([
propDecorator(true)
], MyClass.prototype, "a", void 0);
__decorate([
propDecorator(false, "Hi")
], MyClass, "A", void 0);
return MyClass;
}());
console.log(MyClass.A);
var obj = new MyClass();
console.log(obj.a);
请参考方法装饰器。
参数装饰器
参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文(比如 declare的类)里。
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引。
我们来看一个例子:
function paramDecorator(target: Object, propertyKey: string | symbol, parameterIndex: number) {
console.log(target + ", " + <any> propertyKey + ", " + parameterIndex);
} class MyClass {
public func(@paramDecorator a: number, @paramDecorator b: string = "hello", @paramDecorator c?: boolean): void {
console.log("call method");
}
} var obj = new MyClass();
obj.func(1);
obj.func(2);
输出如下:
[object Object], func, 2
[object Object], func, 1
[object Object], func, 0
call method
call method
我们可以发现,参数装饰器返回的函数会在解释方法的参数时被调用一次,并可以得到参数的相关信息。我们有3个参数就调用了3次。
装饰器方法的调用只会在加载代码时执行一次,调用被装饰的参数的方法不会触发装饰器方法。
编译后的源码:
"use strict";
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;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
function paramDecorator(target, propertyKey, parameterIndex) {
console.log(target + ", " + propertyKey + ", " + parameterIndex);
}
var MyClass = (function () {
function MyClass() {
}
MyClass.prototype.func = function (a, b, c) {
if (b === void 0) { b = "hello"; }
console.log("call method");
};
__decorate([
__param(0, paramDecorator), __param(1, paramDecorator), __param(2, paramDecorator)
], MyClass.prototype, "func", null);
return MyClass;
}());
var obj = new MyClass();
obj.func(1);
obj.func(2);
请参考方法装饰器。