一、是什么
裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法, 訪問符,屬性或參數上
是一種在不改變原類和使用繼承的情況下,動態地擴展對象功能
同樣的,本質也不是什么高大上的結構,就是一個普通的函數,@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
三、應用場景
可以看到,使用裝飾器存在兩個顯著的優點:
- 代碼可讀性變強了,裝飾器命名相當於一個注釋
- 在不改變原有代碼情況下,對原來功能進行擴展
后面的使用場景中,借助裝飾器的特性,除了提高可讀性之后,針對已經存在的類,可以通過裝飾器的特性,在不改變原有代碼情況下,對原來功能進行擴展