隨着 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