僅僅包裝現有的模塊,使之 「更加華麗」 ,並不會影響原有接口的功能 —— 比如你給手機添加一個外殼罷了,並不影響手機原有的通話、充電等功能;前端
ES7
中增長了一個 decorator
屬性,它借鑑自 Python
vue
下面咱們以 鋼鐵俠 爲例講解如何使用 ES7
的 decorator
。git
以鋼鐵俠爲例,鋼鐵俠本質是一我的,只是「裝飾」了不少武器方纔變得那麼 NB
,不過再怎麼裝飾他仍是一我的。github
咱們的示例場景是這樣的編程
Man
類,它的抵禦值 2,攻擊力爲 3,血量爲 3;建立 Man
類:json
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
複製代碼
代碼直接放在 babeljs.io/repl/ 中運行查看結果, 記得勾選
Setting
的Evaluate
選項,和options
的選項爲legacy
babel
建立 decorateArmour
方法,爲鋼鐵俠裝配盔甲——注意 decorateArmour
是裝飾在方法init
上的。app
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,看來盔甲起做用了。函數
Decorators
的本質是利用了 ES5
的 Object.defineProperty
屬性,這三個參數實際上是和 Object.defineProperty
參數一致的ui
在上面的示例中,咱們成功爲 普通人 增長 「盔甲」 這個裝飾;如今我想再給他增長 「光束手套」,但願額外增長 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
而已,能夠方便地增刪;(同時還能夠複用)
裝飾模式有兩種:純粹的裝飾模式 和 半透明的裝飾模式。
上述的兩個 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 }]
]
}
複製代碼