一、是什麼
裝飾器是一種特殊型別的宣告,它能夠被附加到類宣告,方法, 訪問符,屬性或引數上
是一種在不改變原類和使用繼承的情況下,動態地擴充套件物件功能
同樣的,本質也不是什麼高大上的結構,就是一個普通的函式,@expression
的形式其實是Object.defineProperty
的語法糖
expression
求值後必須也是一個函式,它會在執行時被呼叫,被裝飾的宣告資訊做為引數傳入
二、使用方式
由於typescript
是一個實驗性特性,若要使用,需要在tsconfig.json
檔案啟動,如下:
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
typescript
裝飾器的使用和javascript
基本一致
類的裝飾器可以裝飾:
-
類
-
方法/屬性
-
引數
-
訪問器
類裝飾
例如宣告一個函式 addAge
去給 Class 的屬性 age
新增年齡.
function addAge(constructor: Function) { constructor.prototype.age = 18; } @addAge class Person{ name: string; age!: number; constructor() { this.name = 'huihui'; } } let person = new Person(); console.log(person.age); // 18
上述程式碼,實際等同於以下形式:
Person = addAge(function Person() { ... });
上述可以看到,當裝飾器作為修飾類的時候,會把構造器傳遞進去。constructor.prototype.age
就是在每一個例項化物件上面新增一個 age
屬性
方法/屬性裝飾
同樣,裝飾器可以用於修飾類的方法,這時候裝飾器函式接收的引數變成了:
- target:物件的原型
- propertyKey:方法的名稱
- descriptor:方法的屬性描述符
可以看到,這三個屬性實際就是Object.defineProperty
的三個引數,如果是類的屬性,則沒有傳遞第三個引數
如下例子:
// 宣告裝飾器修飾方法/屬性 function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log(target); console.log("prop " + propertyKey); console.log("desc " + JSON.stringify(descriptor) + "\n\n"); descriptor.writable = false; }; function property(target: any, propertyKey: string) { console.log("target", target) console.log("propertyKey", propertyKey) } class Person{ @property name: string; constructor() { this.name = 'huihui'; } @method say(){ return 'instance method'; } @method static run(){ return 'static method'; } } const xmz = new Person(); // 修改例項方法say xmz.say = function() { return 'edit' }
輸出如下圖所示:
引數裝飾
接收3個引數,分別是:
- target :當前物件的原型
- propertyKey :引數的名稱
- index:引數陣列中的位置
function logParameter(target: Object, propertyName: string, index: number) { console.log(target); console.log(propertyName); console.log(index); } class Employee { greet(@logParameter message: string): string { return `hello ${message}`; } } const emp = new Employee(); emp.greet('hello');
輸入如下圖:
訪問器裝飾
使用起來方式與方法裝飾一致,如下:
function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { console.log(target); console.log("prop " + propertyKey); console.log("desc " + JSON.stringify(descriptor) + "\n\n"); }; class Person{ _name: string; constructor() { this._name = 'huihui'; } @modification get name() { return this._name } }
裝飾器工廠
如果想要傳遞引數,使裝飾器變成類似工廠函式,只需要在裝飾器函式內部再函式一個函式即可,如下:
function addAge(age: number) { return function(constructor: Function) { constructor.prototype.age = age } } @addAge(10) class Person{ name: string; age!: number; constructor() { this.name = 'huihui'; } } let person = new Person();
執行順序
當多個裝飾器應用於一個宣告上,將由上至下依次對裝飾器表示式求值,求值的結果會被當作函式,由下至上依次呼叫,例如如下:
function f() { console.log("f(): evaluated"); return function (target, propertyKey: string, descriptor: PropertyDescriptor) { console.log("f(): called"); } } function g() { console.log("g(): evaluated"); return function (target, propertyKey: string, descriptor: PropertyDescriptor) { console.log("g(): called"); } } class C { @f() @g() method() {} } // 輸出 f(): evaluated g(): evaluated g(): called f(): called
三、應用場景
可以看到,使用裝飾器存在兩個顯著的優點:
- 程式碼可讀性變強了,裝飾器命名相當於一個註釋
- 在不改變原有程式碼情況下,對原來功能進行擴充套件
後面的使用場景中,藉助裝飾器的特性,除了提高可讀性之後,針對已經存在的類,可以通過裝飾器的特性,在不改變原有程式碼情況下,對原來功能進行擴充套件