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