早讀《Understanding JavaScript Decorators》

  • 2019 年 12 月 24 日
  • 筆記

Understanding JavaScript Decorators

這是一篇講 JavaScript 裝飾器的文章,目前處於tc39第二階段,由於是提取精髓,因此略有刪減。

裝飾器的收益在於可以抽象一部分通用的程式碼,在共享邏輯上,非常有用,讓我們來看一個對屬性設置只讀的例子:

function readOnly(target, key, descriptor) {    return {      ...descriptor,      writable: false,    };  }    class Oatmeal extends Porridge {    @readOnly viscosity = 20;    // (you can also put @readOnly on the line above the property)      constructor(flavor) {      super();      this.flavor = flavor;    }  }

我們可以理解裝飾器函數傳遞了三個參數給你使用,分別是 target ,key,descriptor,並且最後需要返回元素或類的屬性描述符。

這個屬性描述符相信你使用 defineProperty 時肯定用過,是的,就是定一個對象的行為描述。當你在裝飾屬性或方法時,你可以想像一下,你在操作具體的對象,最後返回一個描述符集合,系統幫助我們完成最後的設定。

function apiRequest(target, key, descriptor) {    const apiAction = async function(...args) {      // More about this line shortly:      const original = descriptor.value || descriptor.initializer.call(this);        this.setNetworkStatus('loading');        try {        const result = await original(...args);        return result;      } catch (e) {        this.setApiError(e);      } finally {        this.setNetworkStatus('idle');      }    };      return {      ...descriptor,      value: apiAction,      initializer: undefined,    };  }    class WidgetStore {    @apiRequest    async getWidget(id) {      const { widget } = await api.getWidget(id);      this.addWidget(widget);      return widget;    }  }

這個 original 就是外部 getWidget 方法。

最後我們再來看一看裝飾類,實際上我們只需要傳遞第一個參數:

function customElement(name) {    return function(target) {      customElements.define(name, target);    };  }    @customElement('intro-message');  class IntroMessage extends HTMLElement {    constructor() {      super();    }  }

最後結論:

https://github.com/tc39/proposal-decorators

目前從公開的文檔上來看,設計小組正準備將它重新設計為靜態裝飾器,未來有沒有變動,暫時未知。目前 TypeScript 和 Babel 都對裝飾器有支援,TS需要手動開啟這個功能。

業務上裝飾器用的恰到好處,確實能共享很多程式碼,但早期採用有一定的成本。