JS中裝飾器到底是什麼?

BIRD發表於2018-07-15

因主要的技術棧是Angular,對於Angular採用的裝飾器特別認可,是一種優雅的攔截JS的方式。

TC39的裝飾器提案其實共3個:class類和類屬性裝飾器、function函式裝飾器、parameter方法引數裝飾器,後2個仍處於Stage 0中,因此本文只針對class裝飾器

目前Decorator仍處於Stage 2的階段,不知道能否在ES2019(ES10)中推出,但一個提案只要能進入Stage 2,就基本會包括在以後的正式標準裡面。

有N多文章寫道Decorator是ES2016(ES7)推出的,不知道這是從哪裡流傳出來的,ES2016最終特性根本就沒有Decorator,可能的原因:Decorator只是有望在ES2016推出的,實際上並沒有。

image


以Angular中的一個元件為例,來說明裝飾器的主要用法和裝飾器到底是什麼:

@Component({
  selector: 'app-transfer-common',
  templateUrl: './transfer-common.component.html',
  styleUrls: ['./transfer-common.component.scss']
})
@AutoUnsubscribe()
export class TransferCommonComponent implements OnInit {
  whichRouter: String;
  btnShowStatus: ShowOrHideBtn;
  queryParamsSubscribe: any;
  userInfo: any = this.commonUserService.getUserInfo(); 
  i18ns_common;
  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private returnVariousBtn: ReturnVariousRouterParamsService,
    private commonUserService: CommonUserService,
    private httpService: HttpService,
    private translate: TranslateService,
  ) { }

  ngOnInit() {
  }

  // 跳轉到新增
  @getProperty
  gotoAdd() {
  }

  /* ngOnDestroy() {
    this.queryParamsSubscribe.unsubscribe();

  } */
}
複製程式碼

一.類裝飾器

AutoUnsubscribe是一個自動取消訂閱的裝飾器,可傳入引數指定某個可訂閱物件不自動取消訂閱。如不需要指定,可取消外層高階函式

export function AutoUnsubscribe(params: string) {
  return function (constructor) {
    console.log(constructor);
    console.log(constructor.prototype);
    const originNgDestory = constructor.prototype.ngOnDestroy;
    constructor.prototype.ngOnDestroy = function () {
      console.log(this);
      console.log(this.constructor);
      console.log(constructor);
      for (const property of Object.values(this)) {
        if (property && (typeof property.unsubscribe === 'function')) {
          console.log(property);
        }
      }
      originNgDestory && typeof originNgDestory === 'function' && originNgDestory.apply(this);
    };
  };
}
複製程式碼

1.類裝飾器僅僅是一個接受一個引數的、被裝飾的類的建構函式,常用於修改、新增類的原型方法

2.類裝飾器傳入的constructor就是類的constructor(特指constructor(){}),如下圖紅色部分.類裝飾器作用於類的 constructor,並且觀察、修改或者替換一個類的定義。

3.this是整個class,如圖天藍色外框 4.特殊的是:類的屬性方法在類的__proto__中,其他的是類的直接子集,如圖靑藍色部分

4.引數constructor === this.constructor

5.執行訂閱的時候,訂閱的是一個Subscribe物件,property.unsubscribe是從原型鏈上獲取的unsubscribe,如下圖綠色部分

image

二.類屬性(方法)裝飾器

// 下一個事件佇列中執行的裝飾器
export const timeoutDecorator = function (milliseconds: number = 0) {
  return function (target, key, descriptor: any) {
    console.log(target);  // 即整個物件
    console.log(key);     // 裝飾器修飾的某個key
    console.log(descriptor);
    const originalMethod = descriptor.value;  // value即這個key對應的方法
    
    /** 當執行gotoAdd方法時,即先執行以下函式 */
    descriptor.value = function (...args) {
      console.log(args);
      setTimeout(() => {
        originalMethod.apply(this, args);       // gotoAdd方法呼叫
      }, milliseconds);
    };
    console.log(descriptor);
    return descriptor;
  };
};

export const getProperty = function (target, name, descriptor) {
  console.log(target);
  console.log(name);
  console.log(descriptor);
  const originalMethod = descriptor.value;
  descriptor.value = function (...args) {
    console.log(args);
    originalMethod.apply(this, args);
  };
  console.log(descriptor);
  return descriptor;
};

複製程式碼

image

1.類方法裝飾器是一個函式,函式引數就是Object.defineProperty中的三個引數即:target(目標物件)、key(呼叫的屬性名)、descriptor(呼叫的屬性的描述,包括configurable、enumerable、writable、value)

2.類方法裝飾器的作用是把類中的方法放入裝飾器中執行,個人理解類似於管道或者攔截器

3.類的普通屬性(不是方法的)也可以新增裝飾器,比如Angular中的@ViewChild,@ViewContent

三.修飾器本質就是編譯時執行的函式

注意,不管哪種修飾器,對類的行為的改變,是程式碼編譯時(初始化時)發生的,而不是在執行時。這意味著,修飾器能在編譯階段執行程式碼。也就是說,修飾器本質就是編譯時執行的函式。

表現上:新增上裝飾器後,裝飾器中descriptor.value = function(...args) ...... 函式外的內容會先執行.然後在每次觸發類中的修飾的方法是會才會呼叫descriptor.value = function(...args) ...... 中的內容,args是指觸發此方法時的實際引數.再通過apply方法,呼叫需要修飾的類方法,注意不一定是指向this,也有可能是target,具體看情況

四.特別注意:this

箭頭函式中的this和普通函式的this是不同的,注意函式體內是否有this,有的話就不要用箭頭函式了,詳情請搜尋箭頭函式的this


劃重點

1.類裝飾器和類方法裝飾器是不同的,但本質都是一個函式
2.類裝飾器常用於修改、新增類的原型方法,類方法裝飾器用於攔截類方法,需要掌握具體寫法
3.注意箭頭函式和this的搭配使用問題

參考資料:

ECMAScript 6 入門

ECMAScript proposals

decorator

相關文章