臨時起的興趣,想寫一篇關於ts decorator的文章,就花小半天整理了一下... 這東西,在ES2017裏好像也有... 文檔的話看這裏。node
由於臨時,就沒想寫太多文字介紹,帶少量文字說明直接開擼代碼吧。git
本文經過ts編譯後的decorator代碼結合兩個案例來解釋一番裝飾器是什麼?能作什麼?有什麼好處?github
實現代碼json
編譯後代碼是這樣的,帶註釋:app
var __decorate = (this && this.__decorate) || function(decorators, target, key, desc) { // c 參數長度 // r ? c < 3 則是target,不然先判斷desc爲null的話則將desc取target的key屬性的描述,再不然即是desc了 // d 預留使用 var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; // 下面文字解釋,這僅是個甩鍋的行爲 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); // 循環 decorators 並每次賦值給 d,而且判斷值 else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) // c < 3 ,用 r 做爲 decorators[i] 的入參執行; // c > 3 ,target, key, r 做爲 decorators[i] 的入參執行; // c === 3,target, key 做爲 decorators[i] 的入參執行。 // 若是執行無返回結果, r = r;。 r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; // 若是 c > 3 && r , 修改 target ,返回 r return c > 3 && r && Object.defineProperty(target, key, r), r; };
從代碼裏能夠看出,最終結果要麼是用 decorator 執行 target ,從而改變一些什麼東西;要麼就是使用 Object.defineProperty 來對 target 來作操做,代碼就幾行,用處確不小... 具體的執行過程結合下面的兩個例子會更容易理解。函數
值得一提的是,關於代碼裏的Reflect本來覺得是這個 sec-reflect-object 裏的方法,但惋惜不是;this
而後猜想是Typescript的實現,翻了 Typescript/tsc.js 的代碼(若是打不開連接就從 node_modules 下看吧),發現也不是;spa
再去查 stackoverflow 的解釋,是這樣的 what-is-reflect-decorate-in-js-code-transpiled-from-tsprototype
大體說是ts但願把這個鍋甩給ES來補,到時候 ts 的 else 裏的代碼即是 polyfill 了日誌
案例結合
如下面的 decorator 和 class 做爲例子解釋
// ts 代碼 function show(target: any) { console.log(target); target.prototype.showMe = (name: string) => { console.log("show me :", name); }; } interface IShow { showMe?(name: string): any; } @show class Show implements IShow { showMe(name: string) {} } const shoow = new Show(); shoow.showMe("ys"); // 編譯後的js // decorator ,簡單的打印,而且修改方法 function show(target) { console.log(target); target.prototype.showMe = function(name) { console.log("show me :", name); }; } // class Shoow var Shoow = (function() { function Shoow() {} Shoow.prototype.showMe = function(name) {}; // decorators 爲[show],target 爲 Shoow Shoow = __decorate([show], Shoow); return Shoow; })(); var shooow = new Shoow(); shooow.showMe("ys"); // output : show me : ys
理解一下執行的步驟:
1. decorators = [show],target = Shoow;
2. c = 2,r = target{Shoow},d = undefined;
3. 不存在 Reflect,走循環,只循環一次;
4. d = show,r = show(target{Shoow}),r 沒返回結果,因此 r 仍是 r , r = target{Shoow};
5. return 結果: c = 2, 因此返回 false;
6. 執行後的無返回值,可是在執行 show(target{Shoow}) 的時候將 showMe 方法改掉了,執行結果符合預期。
一個不夠?再來一個?此次在裏面返回一個函數試試...
// ts代碼 function logger1(config?) { return function(target, key: string, descriptor: PropertyDescriptor) { const _value = descriptor.value; if (typeof _value === "function") { descriptor.value = (...args) => { console.log(`logger1-begin : ${config.level}`); const res = _value.apply(target, args); console.log(`logger1-end`); return res; }; } return descriptor; }; } function logger2(config?) { return function(target, key: string, descriptor: PropertyDescriptor) { const _value = descriptor.value; if (typeof _value === "function") { descriptor.value = (...args) => { console.log(`logger2-begin : ${config.level}`); const res = _value.apply(target, args); console.log(`logger2-end`); return res; }; } return descriptor; }; } interface IShow { showMe?(name: string): any; } class Show implements IShow { @logger1({ level: "info" }) @logger2({ level: "error" }) showMe(name: string) { console.log("show me :", name); } } const shoow = new Show(); shoow.showMe("ys"); // output 這裏手動加個縮進,這時候showMe方法已經通過屢次包裹 // logger1-begin : info // logger2-begin : error // show me : ys // logger2-end // logger1-end
再來看看執行步驟:
1. decorators = [logger1, logger2],target = Shoow,key = "showMe",desc = null 注意,這裏是爲null,不是爲undefined;
2. c = 4,r = target{Shoow},d = undefined;
3. 不存在 Reflect,走循環,只循環一次;
4. 第一次循環取 d = logger1,r = logger1(target, key, r),由於 return 存在值,r = logger1 的返回函數,這時候 descriptor.value 被第一次重寫;
5. 第二次循環取 d = logger2,r = logger2(target, key, r),又由於 return 存在值,r = logger2 的返回函數,這時候 descriptor.value 被第二次重寫;
6. return 結果: 由於 c > 3,r 存在值,執行 Object.defineProperty(target, key, r)來重寫對象屬性而且返回 r (r爲重寫的結果);
7. 通過 2 次重寫 showMe 屬性值,執行結果符合預期。
歡樂
裝飾器給你帶來什麼歡樂?
簡單列幾個最明顯的優勢,其餘在運用中各自提取每日份的快樂去吧...
1. 業務和功能之間的解耦(好比日誌)
// 平常 dosomething(){ consol.log('start') // some thing console.log('end') } // 使用裝飾器 @logger(logConfig?) dosomething();
2. 代碼結構清晰(特別針對多層HOC後的React組件)
// 平常多層HOC class MyComponent extends Component{ // .. } connect(mapFn)( MyHoc(someMapFn)( Form.create(fieldsMapFn)(MyComponent) ) ) // 使用裝飾器 @connect(mapFn) @MyHoc(someMapFn) @FormFields(fieldsMapFn) class MyComponent extends Component{ // .. } export default MyComponent;
最後
注意編譯
tsc --target ES5 --experimentalDecorators
或者 tsconfig.json 里加入
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
順便,AOP瞭解一下...