僅僅包裝現有的模塊,使之 「更加華麗」 ,並不會影響原有接口的功能 —— 比如你給手機添加一個外殼罷了,並不影響手機原有的通話、充電等功能;
ES7
中增長了一個 decorator
屬性,它借鑑自 Python
前端
下面咱們以 鋼鐵俠 爲例講解如何使用 ES7
的 decorator
。vue
以鋼鐵俠爲例,鋼鐵俠本質是一我的,只是「裝飾」了不少武器方纔變得那麼 NB
,不過再怎麼裝飾他仍是一我的。git
咱們的示例場景是這樣的github
Man
類,它的抵禦值 2,攻擊力爲 3,血量爲 3;建立 Man
類:編程
class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } init(def,atk,hp){ this.def = def; // 防護值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } toString(){ return `防護力:${this.def},攻擊力:${this.atk},血量:${this.hp}`; } } var tony = new Man(); console.log(`當前狀態 ===> ${tony}`); // 輸出:當前狀態 ===> 防護力:2,攻擊力:3,血量:3
代碼直接放在 http://babeljs.io/repl/ 中運行查看結果,
記得勾選Setting
的Evaluate
選項,和options
的選項爲legacy
建立 decorateArmour
方法,爲鋼鐵俠裝配盔甲——注意 decorateArmour
是裝飾在方法init
上的。json
function decorateArmour(target, key, descriptor) { const method = descriptor.value; let moreDef = 100; let ret; descriptor.value = (...args)=>{ args[0] += moreDef; ret = method.apply(target, args); return ret; } return descriptor; } class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour init(def,atk,hp){ this.def = def; // 防護值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } toString(){ return `防護力:${this.def},攻擊力:${this.atk},血量:${this.hp}`; } } var tony = new Man(); console.log(`當前狀態 ===> ${tony}`); // 輸出:當前狀態 ===> 防護力:102,攻擊力:3,血量:3
咱們先看輸出結果,防護力的確增長了 100,看來盔甲起做用了。babel
Decorators
的本質是利用了 ES5
的 Object.defineProperty
屬性,這三個參數實際上是和 Object.defineProperty
參數一致的app
在上面的示例中,咱們成功爲 普通人 增長 「盔甲」 這個裝飾;如今我想再給他增長 「光束手套」,但願額外增長 50 點防護值。函數
... function decorateLight(target, key, descriptor) { const method = descriptor.value; let moreAtk = 50; let ret; descriptor.value = (...args)=>{ args[1] += moreAtk; ret = method.apply(target, args); return ret; } return descriptor; } class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour @decorateLight init(def,atk,hp){ this.def = def; // 防護值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } ... } var tony = new Man(); console.log(`當前狀態 ===> ${tony}`); //輸出:當前狀態 ===> 防護力:102,攻擊力:53,血量:3
在這裏你就能看出裝飾模式的優點了,它能夠對某個方法進行疊加使用,對原類的侵入性很是小,只是增長一行@decorateLight
而已,能夠方便地增刪;(同時還能夠複用)this
裝飾模式有兩種:純粹的裝飾模式 和 半透明的裝飾模式。
上述的兩個 demo
中所使用的應該是 純粹的裝飾模式,它並不增長對原有類的接口;下面要講 demo
是給普通人增長「飛行」能力,至關於給類新增一個方法,屬於 半透明的裝飾模式,有點兒像適配器模式的樣子。
... // 3 function addFly(canFly){ return function(target){ target.canFly = canFly; let extra = canFly ? '(技能加成:飛行能力)' : ''; let method = target.prototype.toString; target.prototype.toString = (...args)=>{ return method.apply(target.prototype,args) + extra; } return target; } } @addFly(true) class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour @decorateLight init(def,atk,hp){ this.def = def; // 防護值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } ... } ... console.log(`當前狀態 ===> ${tony}`); // 輸出:當前狀態 ===> 防護力:102,攻擊力:53,血量:3(技能加成:飛行能力)
做用在方法上的 decorator
接收的第一個參數(target
)是類的 prototype
;若是把一個 decorator
做用到類上,則它的第一個參數 target
是 類自己。
Decorator
是針對 Man
的裝飾器基類DecorateArmour
典型地使用 prototype
繼承方式 繼承自 Decorator
基類;IOC
(控制反轉)思想 ,Decorator
是接受 Man
類,而不是本身建立 Man
類;// 首先咱們要建立一個基類 function Man(){ this.def = 2; this.atk = 3; this.hp = 3; } // 裝飾者也須要實現這些方法,遵照 Man 的接口 Man.prototype={ toString:function(){ return `防護力:${this.def},攻擊力:${this.atk},血量:${this.hp}`; } } // 建立裝飾器,接收 Man 對象做爲參數。 var Decorator = function(man){ this.man = man; } // 裝飾者要實現這些相同的方法 Decorator.prototype.toString = function(){ return this.man.toString(); } // 繼承自裝飾器對象 // 建立具體的裝飾器,也是接收 Man 做對參數 var DecorateArmour = function(man){ var moreDef = 100; man.def += moreDef; Decorator.call(this,man); } DecorateArmour.prototype = new Decorator(); // 接下來咱們要爲每個功能建立一個裝飾者對象,重寫父級方法,添加咱們想要的功能。 DecorateArmour.prototype.toString = function(){ return this.man.toString(); } // 注意這裏的調用方式 // 構造器至關於「過濾器」,面向切面的 var tony = new Man(); tony = new DecorateArmour(tony); console.log(`當前狀態 ===> ${tony}`); // 輸出:當前狀態 ===> 防護力:102,攻擊力:3,血量:3
經典應用就是 日誌系統 了,那麼咱們也用 ES7
的語法給鋼鐵俠打造一個日誌系統吧。
/** * Created by jscon on 15/10/16. */ let log = (type) => { return (target, name, descriptor) => { const method = descriptor.value; descriptor.value = (...args) => { console.info(`(${type}) 正在執行: ${name}(${args}) = ?`); let ret; try { ret = method.apply(target, args); console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`); } catch (error) { console.error(`(${type}) 失敗: ${name}(${args}) => ${error}`); } return ret; } } } class IronMan { @log('IronMan 自檢階段') check(){ return '檢查完畢'; } @log('IronMan 攻擊階段') attack(){ return '擊倒敵人'; } @log('IronMan 機體報錯') error(){ throw 'Something is wrong!'; } } var tony = new IronMan(); tony.check(); tony.attack(); tony.error(); // 輸出: // (IronMan 自檢階段) 正在執行: check() = ? // (IronMan 自檢階段) 成功 : check() => 檢查完畢 // (IronMan 攻擊階段) 正在執行: attack() = ? // (IronMan 攻擊階段) 成功 : attack() => 擊倒敵人 // (IronMan 機體報錯) 正在執行: error() = ? // (IronMan 機體報錯) 失敗: error() => Something is wrong!
Logger 方法的關鍵在於:
const method = descriptor.value;
將原有方法提取出來,保障原有方法的純淨;try..catch
語句是 調用 ret = method.apply(target, args);
在調用以前以後分別進行日誌彙報;return ret;
原始的調用結果在JavaScript
中實現AOP
,是把一個函數「動態織入」到另外一個函數之中。
首先要構造Function
的prototype
//prototype.js Function.prototype.before = function (beforefn) { let _self = this; return function () { beforefn.apply(this, arguments); return _self.apply(this, arguments); }; }; Function.prototype.after = function (afterfn) { let _self = this; return function () { let ret = _self.apply(this, arguments); afterfn.apply(this, arguments); return ret; }; }; Function.prototype.around = function (beforefn, afterfn) { let _self = this; return function () { beforefn.apply(this, arguments); let ret = _self.apply(this, arguments); afterfn.apply(this, arguments); return ret; }; };
編輯咱們的裝飾器函數
//decorator.js export const before = function (...args) { return function (target, key, descriptor) { descriptor.value = descriptor.value.before(() => { console.log(`Action-${key} 觸發埋點!`); }); }; }; export const after = function (...args) { return function (target, key, descriptor) { descriptor.value = descriptor.value.after(() => { console.log(`Action-${key} 觸發埋點!`); }); }; }; export const around = function (...args) { return function (target, key, descriptor) { descriptor.value = descriptor.value.around(() => { console.log(`Action-${key} 觸發埋點before!`); }, () => { console.log(`Action-${key} 觸發埋點after!`); }); }; };
編輯咱們的vue
文件
//test.vue <template> <div></div> </template> <script> import { before, after, around } from '@/lib/decorator'; export default { data () { }, methods: { @before() @after() @around() errorHandle () { // 一些共用的異常處理方案 } }, }; </script>
.babelrc文件
{ "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }] ] }