TypeScript 與 ES6 的裝飾器?
- ES6 的裝飾器是一種函數,寫成@ + 函數名。它可以放在類和類方法的定義前面。
- TypeScript 的裝飾器是一種特殊類型的聲明,它能夠被附加到類、方法、訪問符、屬性和參數上。裝飾器使用 @expression 這種形式,expression 求值后必須是一個函數,會在運行時被調用,被裝飾的聲明信息作為參數傳入。
注意:裝飾器是一項實驗性特性,在未來的版本中可能會發生變化
在 TypeScript 中不能直接使用,必須要啟用實驗性裝飾器,可以在命令行或者 tsconfig.json 里啟用 experimentalDecoratiors 編譯器選項:
- 命令行
tsc --target ES5 --experimentalDecorators
- tsconfig.json
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
裝飾器只能裝飾類里面的東西,因為函數存在聲明的提升,所以不能裝飾單個函數。
一、裝飾器工廠
一個最簡單的裝飾器是 @ + 函數名,例如 sealed 裝飾器,定義 sealed 函數如下:
function sealed(target:any) {
console.log(target);
}
@sealed
class A{}
這個裝飾器缺點特別的明顯,我們無法傳入參數,所以出現了裝飾器工廠,裝飾器工廠就是個高階函數,函數返回函數,利于我們傳入參數。
function color(params: string) { // 這是裝飾器工廠
return function(target:any) { // 這是裝飾器
target.prototype.colorName = params;
console.log(params);
console.log(target);
}
}
@color("red")
class Color{
colorName:string = "blue";
constructor(){
}
};
二、組合裝飾器
多個裝飾器可以同時應用到一個聲明上,比如:
書寫在同一行上:
@f @g x
書寫在多行上(推薦):
@f
@g
x
當多個裝飾器應用在同一個聲明上的時候,裝飾器的執行順序為:
- 如果不是裝飾器工廠的話,執行順序由下往上。
function sigle1(target:any) {
console.log(1);
}
function sigle2(target:any) {
console.log(2);
}
function sigle3(target:any) {
console.log(3);
}
@sigle1
@sigle2
@sigle3
class A {
}
//執行結果321
- 如果是裝飾器工廠的話,先執行完工廠函數,然后再去由下而上執行裝飾器函數。
function f() {
console.log("1");
return function (target:any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("2");
}
}
function g() {
console.log("3");
return function (target:any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("4");
}
}
function h() {
console.log("5");
return function (target:any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("6");
}
}
class C {
@f()
@g()
@h()
method() {}
}
//執行結果為135642
三、類裝飾器
類裝飾器表達式會在運行時當做函數被調用,類的構造函數作為其唯一的參數。
function color(params: string) { // 這是裝飾器工廠
return function(target:any) { // 這是裝飾器
target.prototype.colorName = params;
target.prototype.run = ()=>{};
console.log(params);
console.log(target);
}
}
@color("red")
class Color{
colorName:string = "blue";
constructor(){
}
};
可以在不修改原類的情況下,動態修改類的屬性和方法。甚至還可以返回一個類,例如:
function color(target: any) { // 這是裝飾器
return class A extends target {
method(){
console.log("A")
}
}
}
@color
class Color{
method(){
console.log("Color");
}
getData(){
}
};
四、方法裝飾器
方法裝飾器 :聲明在一個方法之前(緊靠著方法聲明)。它會被應用到方法的 屬性描述符 上,可以用來監視、修改或者替換方法定義。
方法裝飾器不能用在聲明文件(.d.ts),重載或者任何外部上下文(比如 declate 類)中。
方法裝飾器表達式會在運行時當做函數被調用,傳入下面3個參數:
對于靜態成員來說是類的構造函數,對于實例成員來說是類的原型對象
成員的名字
成員的 屬性描述符
注意:如果代碼輸出目標版本小于 ES5,屬性描述符是 undefined,如果代碼輸出目標版本小雨 ES5,屬性返回值會被忽略。
如果方法裝飾器返回一個值,他會被用作方法的 屬性描述符。
下面是一個方法裝飾器 @enumerable 的例子,應用到 Greeter 類的方法上:
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target,propertyKey,descriptor);
console.log("裝飾的函數"+descriptor.value);
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
這里的 @enumerable(false) 是一個裝飾工廠,當裝飾器 @enumerable(false) 被調用的時候,它會修改屬性描述符的 enumerable 屬性。
五、參數裝飾器
參數裝飾器:聲明在一個參數聲明之前(緊靠著參數聲明)。 參數裝飾器應用于類構造函數或方法聲明。 參數裝飾器不能用在聲明文件(.d.ts),重載或其它外部上下文(比如 declare 的類)里。
參數裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:
對于靜態成員來說是類的構造函數,對于實例成員是類的原型對象。
此函數的名字。
參數在函數參數列表中的索引。
注意: 參數裝飾器只能用來監視一個方法的參數是否被傳入。
下例定義了參數裝飾器(@getParams)并應用于 A 類方法的一個參數:
function getParams(value: boolean) {
return function (target: any, name: string, index: number) {
console.log(target,name,index);
};
}
class A {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet(@getParams(false)str:string) {
return "Hello, " + this.greeting;
}
}
六、屬性裝飾器
屬性裝飾器聲明在一個屬性聲明之前(緊靠著屬性聲明)。 屬性裝飾器不能用在聲明文件中(.d.ts),或者任何外部上下文(比如 declare 的類)里。
屬性裝飾器表達式會在運行時當作函數被調用,傳入下列2個參數:
- 對于靜態成員來說是類的構造函數,對于實例成員是類的原型對象。
- 成員的名字。
注意? 屬性描述符 不會做為參數傳入屬性裝飾器,這與 TypeScrip t是如何初始化屬性裝飾器的有關。
因為目前沒有辦法在定義一個原型對象的成員時描述一個實例屬性,并且沒辦法監視或修改一個屬性的初始化方法。
返回值也會被忽略。
因此,屬性描述符只能用來監視類中是否聲明了某個名字的屬性。
function getProperty(value: boolean) {
return function (target: any, name: string) {
console.log(target,name);
};
}
class A {
@getProperty(false)
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
七、訪問符裝飾器
訪問器裝飾器 聲明在一個訪問器的聲明之前(緊靠著訪問器聲明)。訪問器裝飾器應用于訪問器的 屬性描述符 并且可以用來監視、修改或者替換一個訪問器的定義。訪問器裝飾器不能用于聲明文件中(.d.ts),或者任何外部上下文(比如 declare 的類)中。
注意: TypeScript 不允許同時裝飾一個成員的 get 和 set 裝飾器。取而代之的是,一個成員的所有裝飾器必須應用在文檔順序的第一個訪問器上。
這是因為,在裝飾器應用于一個 屬性描述符 時,它聯合了 get 和 set 訪問器,而不是分開聲明的。
訪問器裝飾器表達式會在運行時當做函數被調用,傳入下面3個參數:
- 對于靜態成員是類的構造函數,對于實例成員是類的原型對象
- 成員的名字
- 成員的 屬性描述符
注意:如果代碼輸出目標版本小于 ES5, property descriptor 成員屬性描述符將會是 undefined
如果訪問器返回一個值,它會被用作方法的 屬性描述符。
注意:如果代碼輸出目標版本小于 ES5,返回值會被忽略
function getProperty(value: boolean) {
return function (target: any, name: string) {
console.log(target,name);
};
}
function configurable(val: boolean) {
return function (target: any, name: string, desc: PropertyDescriptor) {
console.log(target,name , desc);
};
}
class A {
@getProperty(false)
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@configurable(false)
get props(){return 111}
set props(value:any){return value}
}
八、裝飾器求值順序
裝飾器的執行順序:
function a(target:any) {
console.log(1)
}
function b(target:any,name:string) {
console.log(2)
}
function c(target:any,name:string) {
console.log(3)
}
function d(target:any,name:string) {
console.log(4)
}
function e(target:any,name:string,index:number) {
console.log(5)
}
@a
class A {
@b
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@d
method(@e str:string){
}
@c
get props(){return 111}
}
//25431
屬性裝飾器<= 參數裝飾器<=方法裝飾器<=訪問符裝飾器<=類裝飾器