一、是什麼

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

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

同樣的,本質也不是什麼高大上的結構,就是一個普通的函式,@expression 的形式其實是Object.defineProperty的語法糖

expression求值後必須也是一個函式,它會在執行時被呼叫,被裝飾的宣告資訊做為引數傳入

二、使用方式

由於typescript是一個實驗性特性,若要使用,需要在tsconfig.json檔案啟動,如下:

  1. {
  2. "compilerOptions": {
  3. "target": "ES5",
  4. "experimentalDecorators": true
  5. }
  6. }
  1.  

typescript裝飾器的使用和javascript基本一致

類的裝飾器可以裝飾:

  • 方法/屬性

  • 引數

  • 訪問器

類裝飾

例如宣告一個函式 addAge 去給 Class 的屬性 age 新增年齡.

  1. function addAge(constructor: Function) {
  2. constructor.prototype.age = 18;
  3. }
  4.  
  5. @addAge
  6. class Person{
  7. name: string;
  8. age!: number;
  9. constructor() {
  10. this.name = 'huihui';
  11. }
  12. }
  13.  
  14. let person = new Person();
  15.  
  16. console.log(person.age); // 18
  1.  

上述程式碼,實際等同於以下形式:

  1. Person = addAge(function Person() { ... });

上述可以看到,當裝飾器作為修飾類的時候,會把構造器傳遞進去。constructor.prototype.age 就是在每一個例項化物件上面新增一個 age 屬性

方法/屬性裝飾

同樣,裝飾器可以用於修飾類的方法,這時候裝飾器函式接收的引數變成了:

  • target:物件的原型
  • propertyKey:方法的名稱
  • descriptor:方法的屬性描述符

可以看到,這三個屬性實際就是Object.defineProperty的三個引數,如果是類的屬性,則沒有傳遞第三個引數

如下例子:

  1. // 宣告裝飾器修飾方法/屬性
  2. function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  3. console.log(target);
  4. console.log("prop " + propertyKey);
  5. console.log("desc " + JSON.stringify(descriptor) + "\n\n");
  6. descriptor.writable = false;
  7. };
  8.  
  9. function property(target: any, propertyKey: string) {
  10. console.log("target", target)
  11. console.log("propertyKey", propertyKey)
  12. }
  13.  
  14. class Person{
  15. @property
  16. name: string;
  17. constructor() {
  18. this.name = 'huihui';
  19. }
  20.  
  21. @method
  22. say(){
  23. return 'instance method';
  24. }
  25.  
  26. @method
  27. static run(){
  28. return 'static method';
  29. }
  30. }
  31.  
  32. const xmz = new Person();
  33.  
  34. // 修改例項方法say
  35. xmz.say = function() {
  36. return 'edit'
  37. }
  1.  

輸出如下圖所示:

引數裝飾

接收3個引數,分別是:

  • target :當前物件的原型
  • propertyKey :引數的名稱
  • index:引數陣列中的位置
  1. function logParameter(target: Object, propertyName: string, index: number) {
  2. console.log(target);
  3. console.log(propertyName);
  4. console.log(index);
  5. }
  6.  
  7. class Employee {
  8. greet(@logParameter message: string): string {
  9. return `hello ${message}`;
  10. }
  11. }
  12. const emp = new Employee();
  13. emp.greet('hello');
  1.  

輸入如下圖:

訪問器裝飾

使用起來方式與方法裝飾一致,如下:

  1.  
  1. function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  2. console.log(target);
  3. console.log("prop " + propertyKey);
  4. console.log("desc " + JSON.stringify(descriptor) + "\n\n");
  5. };
  6.  
  7. class Person{
  8. _name: string;
  9. constructor() {
  10. this._name = 'huihui';
  11. }
  12.  
  13. @modification
  14. get name() {
  15. return this._name
  16. }
  17. }
  1.  

裝飾器工廠

如果想要傳遞引數,使裝飾器變成類似工廠函式,只需要在裝飾器函式內部再函式一個函式即可,如下:

  1. function addAge(age: number) {
  2. return function(constructor: Function) {
  3. constructor.prototype.age = age
  4. }
  5. }
  6.  
  7. @addAge(10)
  8. class Person{
  9. name: string;
  10. age!: number;
  11. constructor() {
  12. this.name = 'huihui';
  13. }
  14. }
  15.  
  16. let person = new Person();
  1.  

執行順序

當多個裝飾器應用於一個宣告上,將由上至下依次對裝飾器表示式求值,求值的結果會被當作函式,由下至上依次呼叫,例如如下:

  1. function f() {
  2. console.log("f(): evaluated");
  3. return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
  4. console.log("f(): called");
  5. }
  6. }
  7.  
  8. function g() {
  9. console.log("g(): evaluated");
  10. return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
  11. console.log("g(): called");
  12. }
  13. }
  14.  
  15. class C {
  16. @f()
  17. @g()
  18. method() {}
  19. }
  20.  
  21. // 輸出
  22. f(): evaluated
  23. g(): evaluated
  24. g(): called
  25. f(): called
  1.  

三、應用場景

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

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

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