JS 裝飾器解析

隨着 ES6 和 TypeScript 中類的引入,在某些場景須要在不改變原有類和類屬性的基礎上擴展些功能,這也是裝飾器出現的緣由。javascript

裝飾器簡介

做爲一種能夠動態增刪功能模塊的模式(好比 redux 的中間件機制),裝飾器一樣具備很強的動態靈活性,只需在類或類屬性以前加上 @方法名 就完成了相應的類或類方法功能的變化。html

不過裝飾器模式仍處於第 2 階段提案中,使用它以前須要使用 babel 模塊 transform-decorators-legacy 編譯成 ES5 或 ES6。java

在 TypeScript 的 lib.es5.d.ts 中,定義了 4 種不一樣裝飾器的接口,其中裝飾類以及裝飾類方法的接口定義以下所示:react

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

下面對這兩種狀況進行解析。git

做用於類的裝飾器

當裝飾的對象是類時,咱們操做的就是這個類自己es6

@log
class MyClass { }

function log(target) { // 這個 target 在這裏就是 MyClass 這個類
   target.prototype.logger = () => `${target.name} 被調用`
}

const test = new MyClass()
test.logger() // MyClass 被調用

因爲裝飾器是表達式,咱們也能夠在裝飾器後面再添加提個參數:github

@log('hi')
class MyClass { }

function log(text) {
  return function(target) {
    target.prototype.logger = () => `${text},${target.name} 被調用`
  }
}

const test = new MyClass()
test.logger() // hello,MyClass 被調用

在使用 redux 中,咱們最常使用 react-redux 的寫法以下:redux

@connect(mapStateToProps, mapDispatchToProps)
export default class MyComponent extends React.Component {}

通過上述分析,咱們知道了上述寫法等價於下面這種寫法:babel

class MyComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

做用於類方法的裝飾器

與裝飾類不一樣,對類方法的裝飾本質是操做其描述符。能夠把此時的裝飾器理解成是 Object.defineProperty(obj, prop, descriptor) 的語法糖,看以下代碼:函數

class C {
  @readonly(false)
  method() { console.log('cat') }
}

function readonly(value) {
  return function (target, key, descriptor) { // 此處 target 爲 C.prototype; key 爲 method;
    // 原 descriptor 爲:{ value: f, enumarable: false, writable: true, configurable: true }
    descriptor.writable = value
    return descriptor
  }
}

const c = new C()
c.method = () => console.log('dog')

c.method() // cat

能夠看到裝飾器函數接收的三個參數與 Object.defineProperty 是徹底同樣的,具體實現能夠看 babel 轉化後的代碼,主要實現以下所示:

var C = (function() {
  class C {
    method() { console.log('cat') }
  }

  var temp
  temp = readonly(false)(C.prototype, 'method',
    temp = Object.getOwnPropertyDescriptor(C.prototype, 'method')) || temp // 經過 Object.getOwnPropertyDescriptor 獲取到描述符傳入到裝飾器函數中

  if (temp) Object.defineProperty(C.prototype, 'method', temp)
  return C
})()

再將再來看看若是有多個裝飾器做用於同一個方法上呢?

class C {
  @readonly(false)
  @log
  method() { }
}

經 babel 轉化後的代碼以下:

desc = [readonly(false), log]
    .slice()
    .reverse()
    .reduce(function(desc, decorator) {
      return decorator(target, property, desc) || desc;
    }, desc);

能夠清晰地看出,通過 reverse 倒序後,裝飾器方法會至裏向外執行。

相關連接

javascript-decorators
Javascript 中的裝飾器
JS 裝飾器(Decorator)場景實戰
修飾器
Babel

相關文章
相關標籤/搜索