仅仅包装现有的模块,使之 “更加华丽” ,并不会影响原有接口的功能 —— 比如你给手机添加一个外壳罢了,并不影响手机原有的通话、充电等功能;
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 }] ] }