裝飾器簡介
裝飾器(Decorators)為我們在類的聲明及成員上通過元編程語法添加標注提供了一種方式。
需要注意的是:裝飾器是一項實驗性特性,在未來的版本中可能會發生改變。
若要啟用實驗性的裝飾器特性,你必須在命令行或tsconfig.json里啟用experimentalDecorators編譯器選項:
1 { 2 "compilerOptions": { 3 "target": "ES5", 4 "experimentalDecorators": true 5 } 6 }
如何定義
裝飾器使用@expression這種形式,expression求值后必須為一個函數,它會在運行時被調用,被裝飾的聲明信息做為參數傳入。
裝飾器組合
多個裝飾器可以同時應用到一個聲明上,就像下面的示例:
書寫在同一行上:
@f @g x
書寫在多行上:
@f
@g
x
在TypeScript里,當多個裝飾器應用在一個聲明上時會進行如下步驟的操作:
- 由上至下依次對裝飾器表達式求值。
- 求值的結果會被當作函數,由下至上依次調用。
類裝飾器
類裝飾器在類聲明之前被聲明(緊靠着類聲明)。 類裝飾器應用於類構造函數,可以用來監視,修改或替換類定義。 類裝飾器不能用在聲明文件中( .d.ts),也不能用在任何外部上下文中(比如declare的類)。
類裝飾器表達式會在運行時當作函數被調用,類的構造函數作為其唯一的參數。
我們來看一個例子:
1 function sealed(constructor: Function) { 2 Object.seal(constructor); 3 Object.seal(constructor.prototype); 4 } 5 6 @sealed 7 class MyClass { 8 a: number = 0; 9 b: string = "hello"; 10 } 11 12 var obj = new MyClass(); 13 // obj.c = true; // 編譯報錯
通過類裝飾器我們可以對類的原型對象做一定的修改。
編譯后的源碼:
1 "use strict"; 2 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 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; 6 return c > 3 && r && Object.defineProperty(target, key, r), r; 7 }; 8 function sealed(constructor) { 9 Object.seal(constructor); 10 Object.seal(constructor.prototype); 11 } 12 var MyClass = (function () { 13 function MyClass() { 14 this.a = 0; 15 this.b = "hello"; 16 } 17 MyClass = __decorate([ 18 sealed 19 ], MyClass); 20 return MyClass; 21 }()); 22 var obj = new MyClass(); 23 // obj.c = true; // 編譯報錯
這里我們只關注傳遞到裝飾函數的constructor參數:就是我們定義的MyClass對象。
方法裝飾器
方法裝飾器聲明在一個方法的聲明之前(緊靠着方法聲明)。 它會被應用到方法的 屬性描述符上,可以用來監視,修改或者替換方法定義。 方法裝飾器不能用在聲明文件( .d.ts),重載或者任何外部上下文(比如declare的類)中。
方法裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:
- 對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
- 成員的名字。
- 成員的屬性描述符。
注意:如果代碼輸出目標版本小於ES5,屬性描述符將會是undefined。
我們來看下面的例子:
1 function methodDecorator(param1: boolean, param2?: string) { 2 return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { 3 console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor)); 4 }; 5 } 6 7 class MyClass { 8 @methodDecorator(true, "this is static") 9 public static sFunc(): void { 10 console.log("call static method"); 11 } 12 13 @methodDecorator(false) 14 public func(): void { 15 console.log("call method"); 16 } 17 } 18 19 MyClass.sFunc(); 20 MyClass.sFunc(); 21 22 var obj = new MyClass(); 23 obj.func(); 24 obj.func();
輸出如下:
1 false, undefined, [object Object], func, {"writable":true,"enumerable":true,"configurable":true} 2 true, this is static, function MyClass() { 3 }, sFunc, {"writable":true,"enumerable":true,"configurable":true} 4 call static method 5 call static method 6 call method 7 call method
我們可以發現,方法裝飾器返回的函數會在解釋類的對應方法時被調用一次,並可以得到裝飾器的參數和被裝飾的方法的相關信息。
裝飾器方法的調用只會在加載代碼時執行一次,調用被裝飾的方法不會觸發裝飾器方法。
編譯后的源碼:
1 "use strict"; 2 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 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; 6 return c > 3 && r && Object.defineProperty(target, key, r), r; 7 }; 8 function methodDecorator(param1, param2) { 9 return function (target, propertyKey, descriptor) { 10 console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor)); 11 }; 12 } 13 var MyClass = (function () { 14 function MyClass() { 15 } 16 MyClass.sFunc = function () { 17 console.log("call static method"); 18 }; 19 MyClass.prototype.func = function () { 20 console.log("call method"); 21 }; 22 __decorate([ 23 methodDecorator(false) 24 ], MyClass.prototype, "func", null); 25 __decorate([ 26 methodDecorator(true, "this is static") 27 ], MyClass, "sFunc", null); 28 return MyClass; 29 }()); 30 MyClass.sFunc(); 31 MyClass.sFunc(); 32 var obj = new MyClass(); 33 obj.func(); 34 obj.func();
這里我們只關注傳遞到裝飾函數的target參數:
裝飾靜態方法時是定義的MyClass對象;
裝飾動態方法時是定義的MyClass對象的原型對象,在24行這里“], MyClass.prototype, "func", null);”,我們知道,MyClass的原型對象在沒有繼承時是一個Object對象,該對象是所有MyClass的實例共用的,在有繼承時,MyClass的原型對象會是一個新創建的對象,並且會對父類的方法進行淺復制的對象,如下:
1 var __extends = (this && this.__extends) || (function () { 2 var extendStatics = Object.setPrototypeOf || 3 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 return function (d, b) { 6 extendStatics(d, b); 7 function __() { this.constructor = d; } 8 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 }; 10 })(); 11 // ... 12 var MyClass = (function () { 13 function MyClass() { 14 } 15 // ... 16 return MyClass; 17 }()); 18 var MyClass2 = (function (_super) { 19 __extends(MyClass2, _super); 20 function MyClass2() { 21 return _super !== null && _super.apply(this, arguments) || this; 22 } 23 // ... 24 return MyClass2; 25 }(MyClass));
此時如果操作target對象,比如為該對象添加一個屬性,那么這個屬性MyClass類的所有實例都可以訪問到。
訪問器裝飾器
訪問器裝飾器聲明在一個訪問器的聲明之前(緊靠着訪問器聲明)。 訪問器裝飾器應用於訪問器的 屬性描述符並且可以用來監視,修改或替換一個訪問器的定義。 訪問器裝飾器不能用在聲明文件中(.d.ts),或者任何外部上下文(比如 declare的類)里。
訪問器裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:
- 對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
- 成員的名字。
- 成員的屬性描述符。
注意:如果代碼輸出目標版本小於ES5,Property Descriptor將會是undefined。
同時TypeScript不允許同時裝飾一個成員的get和set訪問器。取而代之的是,一個成員的所有裝飾的必須應用在文檔順序的第一個訪問器上。這是因為,在裝飾器應用於一個屬性描述符時,它聯合了get和set訪問器,而不是分開聲明的。
我們來看一個例子:
1 function methodDecorator(param1: boolean, param2?: string) { 2 return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { 3 console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor)); 4 }; 5 } 6 7 class MyClass { 8 private static _myName: string; 9 10 @methodDecorator(true, "this is static") 11 public static set myName(value: string) { 12 this._myName = value; 13 } 14 15 public static get myName(): string { 16 return this._myName; 17 } 18 19 private _age: number; 20 21 @methodDecorator(false) 22 public set age(value: number) { 23 this._age = value; 24 } 25 26 public get age(): number { 27 return this._age; 28 } 29 } 30 31 MyClass.myName = "hello"; 32 console.log(MyClass.myName); 33 34 var obj = new MyClass(); 35 obj.age = 28; 36 console.log(obj.age);
輸出如下:
1 false, undefined, [object Object], age, {"enumerable":true,"configurable":true} 2 true, this is static, function MyClass() { 3 }, myName, {"enumerable":true,"configurable":true} 4 hello 5 28
我們可以發現,訪問器裝飾器返回的函數會在解釋類的對應訪問器時被調用一次,並可以得到裝飾器的參數和被裝飾的訪問器的相關信息。
裝飾器方法的調用只會在加載代碼時執行一次,調用被裝飾的訪問器不會觸發裝飾器方法。
編譯后的源碼:
1 "use strict"; 2 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 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; 6 return c > 3 && r && Object.defineProperty(target, key, r), r; 7 }; 8 function methodDecorator(param1, param2) { 9 return function (target, propertyKey, descriptor) { 10 console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor)); 11 }; 12 } 13 var MyClass = (function () { 14 function MyClass() { 15 } 16 Object.defineProperty(MyClass, "myName", { 17 get: function () { 18 return this._myName; 19 }, 20 set: function (value) { 21 this._myName = value; 22 }, 23 enumerable: true, 24 configurable: true 25 }); 26 Object.defineProperty(MyClass.prototype, "age", { 27 get: function () { 28 return this._age; 29 }, 30 set: function (value) { 31 this._age = value; 32 }, 33 enumerable: true, 34 configurable: true 35 }); 36 __decorate([ 37 methodDecorator(false) 38 ], MyClass.prototype, "age", null); 39 __decorate([ 40 methodDecorator(true, "this is static") 41 ], MyClass, "myName", null); 42 return MyClass; 43 }()); 44 MyClass.myName = "hello"; 45 console.log(MyClass.myName); 46 var obj = new MyClass(); 47 obj.age = 28; 48 console.log(obj.age);
請參考方法裝飾器。
屬性裝飾器
屬性裝飾器聲明在一個屬性聲明之前(緊靠着屬性聲明)。 屬性裝飾器不能用在聲明文件中(.d.ts),或者任何外部上下文(比如 declare的類)里。
屬性裝飾器表達式會在運行時當作函數被調用,傳入下列2個參數:
- 對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
- 成員的名字。
我們來看一個例子:
1 function propDecorator(param1: boolean, param2?: string) { 2 return function (target: any, propertyKey: string) { 3 console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey); 4 }; 5 } 6 7 class MyClass { 8 @propDecorator(false, "Hi") 9 public static A: number = 0; 10 11 @propDecorator(true) 12 public a: string = "hello"; 13 } 14 15 console.log(MyClass.A); 16 var obj = new MyClass(); 17 console.log(obj.a);
輸出如下:
1 true, undefined, [object Object], a 2 false, Hi, function MyClass() { 3 this.a = "hello"; 4 }, A 5 0 6 hello
我們可以發現,屬性裝飾器返回的函數會在解釋類的對應屬性時被調用一次,並可以得到裝飾器的參數和被裝飾的屬性的相關信息。
裝飾器方法的調用只會在加載代碼時執行一次,調用被裝飾的屬性不會觸發裝飾器方法。
編譯后的源碼:
1 "use strict"; 2 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 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; 6 return c > 3 && r && Object.defineProperty(target, key, r), r; 7 }; 8 function propDecorator(param1, param2) { 9 return function (target, propertyKey) { 10 console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey); 11 }; 12 } 13 var MyClass = (function () { 14 function MyClass() { 15 this.a = "hello"; 16 } 17 MyClass.A = 0; 18 __decorate([ 19 propDecorator(true) 20 ], MyClass.prototype, "a", void 0); 21 __decorate([ 22 propDecorator(false, "Hi") 23 ], MyClass, "A", void 0); 24 return MyClass; 25 }()); 26 console.log(MyClass.A); 27 var obj = new MyClass(); 28 console.log(obj.a);
請參考方法裝飾器。
參數裝飾器
參數裝飾器聲明在一個參數聲明之前(緊靠着參數聲明)。 參數裝飾器應用於類構造函數或方法聲明。 參數裝飾器不能用在聲明文件(.d.ts),重載或其它外部上下文(比如 declare的類)里。
參數裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:
- 對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
- 成員的名字。
- 參數在函數參數列表中的索引。
我們來看一個例子:
1 function paramDecorator(target: Object, propertyKey: string | symbol, parameterIndex: number) { 2 console.log(target + ", " + <any> propertyKey + ", " + parameterIndex); 3 } 4 5 class MyClass { 6 public func(@paramDecorator a: number, @paramDecorator b: string = "hello", @paramDecorator c?: boolean): void { 7 console.log("call method"); 8 } 9 } 10 11 var obj = new MyClass(); 12 obj.func(1); 13 obj.func(2);
輸出如下:
1 [object Object], func, 2 2 [object Object], func, 1 3 [object Object], func, 0 4 call method 5 call method
我們可以發現,參數裝飾器返回的函數會在解釋方法的參數時被調用一次,並可以得到參數的相關信息。我們有3個參數就調用了3次。
裝飾器方法的調用只會在加載代碼時執行一次,調用被裝飾的參數的方法不會觸發裝飾器方法。
編譯后的源碼:
1 "use strict"; 2 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 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; 6 return c > 3 && r && Object.defineProperty(target, key, r), r; 7 }; 8 var __param = (this && this.__param) || function (paramIndex, decorator) { 9 return function (target, key) { decorator(target, key, paramIndex); } 10 }; 11 function paramDecorator(target, propertyKey, parameterIndex) { 12 console.log(target + ", " + propertyKey + ", " + parameterIndex); 13 } 14 var MyClass = (function () { 15 function MyClass() { 16 } 17 MyClass.prototype.func = function (a, b, c) { 18 if (b === void 0) { b = "hello"; } 19 console.log("call method"); 20 }; 21 __decorate([ 22 __param(0, paramDecorator), __param(1, paramDecorator), __param(2, paramDecorator) 23 ], MyClass.prototype, "func", null); 24 return MyClass; 25 }()); 26 var obj = new MyClass(); 27 obj.func(1); 28 obj.func(2);
請參考方法裝飾器。
