TypeScript 中裝飾器的理解?應用場景?

喆星高照發表於2021-09-15

 

 

 

一、是什麼

裝飾器是一種特殊型別的宣告,它能夠被附加到類宣告,方法, 訪問符,屬性或引數上

是一種在不改變原類和使用繼承的情況下,動態地擴充套件物件功能

同樣的,本質也不是什麼高大上的結構,就是一個普通的函式,@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

 

 

三、應用場景

可以看到,使用裝飾器存在兩個顯著的優點:

  • 程式碼可讀性變強了,裝飾器命名相當於一個註釋
  • 在不改變原有程式碼情況下,對原來功能進行擴充套件

後面的使用場景中,藉助裝飾器的特性,除了提高可讀性之後,針對已經存在的類,可以通過裝飾器的特性,在不改變原有程式碼情況下,對原來功能進行擴充套件

相關文章