Decorator 提供了一種獨特的抽象邏輯,可在原有代碼基礎上,零侵入添加新功能特性。商業代碼老是多種交織並存的,在平常開發中,除了實現業務功能以外,咱們還須要考慮諸如:異常處理、性能分析、日誌等額外的需求。未經設計的的開發方法會傾向於將各類需求耦合組成一個功能模塊,好比:javascript
class Math{ static add(num1,num2){ try{ console.time('some label'); log('log for something'); const result= num1+num2; console.timeEnd('some label'); return result; }catch(e){ error('something had broken'); } } }
上述簡單的兩數相加功能,在添加各種需求以後,已經變的面目全非。Decorator 語法經過描述,可將功能特性疊加到原有功能中:java
class Math{ @log @error @time static add(num1,num2){ return num1+num2; } }
Decorator 就是一個的包裹函數,運行時在編譯階段調用該函數,修改目標對象的行爲、屬性。咱們先來看一個簡單實例:python
const log = (target,prop)=>console.log(`Wrap function: '${prop}'`); const tec={ @log say(){ console.log('hello world') } } // => Wrap function 'say'
Decorator 函數簽名以下:git
// @param target 做用對象 // @param prop 做用的屬性名 // @param descriptor 屬性描述符 // @return descriptor 屬性描述符 function decorator(target,prop,descriptor){}
參數詳解:github
target : 做用的對象,有以下狀況:babel
做用於 class 時,target 爲該 class 函數app
做用於 class 中的函數、屬性 時,target 爲該 class 的 prototype 對象函數
做用於 對象字面量中的函數、屬性 時,target 爲該對象性能
prop : 描述的屬性名,若decorator做用於class時,該參數爲空this
descriptor : 屬性本來的描述符,該描述符可經過 Object.getOwnPropertyDescriptor() 獲取,若decorator做用於class時,該參數爲空
decorator 函數支持返回描述符或 undefined,當返回值爲描述符時,運行時會調用Object.defineProperty()修改原有屬性。
理解 Decorator 機制,最佳方式是使用ES5實現該過程。
class裝飾器機制比較簡單,僅作一層包裝,僞代碼:
// 調用實例 @log class Person{} // 實現代碼 const Person = log(Person);
屬性裝飾器機制則比較複雜,babel 就此提供了一個參考範例:
// decorator 處理 function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object['define' + 'Property'](target, property, desc); desc = null; } return desc; } // 調用實例 class Person{ @log say(){} } // 實現代碼 _applyDecoratedDescriptor( Person.prototype, 'say', [log], Object.getOwnPropertyDescriptor(Person.prototype, 'say'), Person.prototype) )
Decorator 主要應用於以下幾類對象:
class
class 中,除構造函數外的方法
class 中的屬性
對象字面量中的函數
對象字面量中的屬性
// 類 @log class Person{ // 函數 @log say(){} // 屬性 @log name = 'tec'; } // 一樣適用於對象字面量的方法、屬性 const tec = { @log name:'tec', @log walk(){} };
在JS中,Decorator 是一個新概念,對於多數沒有接觸過諸如python、C#的開發者而言,很難理解實際應用場景。幸運的是github已經有人封裝了經常使用Decorator。筆者分析該庫,總結以下幾種定義模式:
經過 descriptor 的 value 值修改:
function decorate(target, key, descriptor) { const fn = descriptor.value; return { ...descriptor, value() { return fn.apply(this, arguments); } } }
經過 descriptor 的 get、set 函數修改:
function decorate(target, key, descriptor) { let value = descriptor.value; return { ...descriptor, get() { return value; } set(v) { value=v; } } }
經過 descriptor 的 writable、enumerable 等屬性修改:
function readonly(target, key, descriptor) { return { ...descriptor, writable:false } }
針對 class ,返回包裹函數
function log(target){ let initTimes=0; return (...arg)=>{ console.log(++initTimes); target.call(this,...arg); }; }
在實際開發中,還須要注意如下事項:
Decorator 的目標是在原有功能基礎上,添加功能,切忌覆蓋原有功能
Decorator 不是管道模式,decorator之間不存在交互,因此必須注意保持decorator獨立性、透明性
Decorator 更適用於非業務功能需求
肯定 decorator 的用途後,切記執行判斷參數類型
decorator 針對每一個裝飾目標,僅執行一次
參考資料: