# ES7-Decorator-装饰者模式
# 装饰模式
仅仅包装现有的模块,使之 “更加华丽” ,并不会影响原有接口的功能 —— 好比你给手机添加一个外壳罢了,并不影响手机原有的通话、充电等功能;
# 使用 ES7 的 decorator
ES7
中增加了一个 decorator
属性,它借鉴自 Python
下面我们以 钢铁侠 为例讲解如何使用 ES7
的 decorator
。
以钢铁侠为例,钢铁侠本质是一个人,只是“装饰”了很多武器方才变得那么 NB
,不过再怎么装饰他还是一个人。
我们的示例场景是这样的
- 首先创建一个普通的
Man
类,它的抵御值 2,攻击力为 3,血量为 3; - 然后我们让其带上钢铁侠的盔甲,这样他的抵御力增加 100,变成 102;
- 让其带上光束手套,攻击力增加 50,变成 53;
- 最后让他增加“飞行”能力
# 【Demo 1】对方法的装饰:装备盔甲
创建 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
代码直接放在 http://babeljs.io/repl/ 中运行查看结果, 记得勾选
Setting
的Evaluate
选项,和options
的选项为legacy
创建 decorateArmour
方法,为钢铁侠装配盔甲——注意 decorateArmour
是装饰在方法init
上的。
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
我们先看输出结果,防御力的确增加了 100,看来盔甲起作用了。
Decorators
的本质是利用了 ES5
的 Object.defineProperty
属性,这三个参数其实是和 Object.defineProperty
参数一致的
# 【Demo 2】装饰器叠加:增加光束手套
在上面的示例中,我们成功为 普通人 增加 “盔甲” 这个装饰;现在我想再给他增加 “光束手套”,希望额外增加 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在这里你就能看出装饰模式的优势了,它可以对某个方法进行叠加使用,对原类的侵入性非常小,只是增加一行@decorateLight
而已,可以方便地增删;(同时还可以复用)
# 【Demo 3】对类的装饰:增加飞行能力
装饰模式有两种:纯粹的装饰模式 和 半透明的装饰模式。
上述的两个 demo
中所使用的应该是 纯粹的装饰模式,它并不增加对原有类的接口;下面要讲 demo
是给普通人增加“飞行”能力,相当于给类新增一个方法,属于 半透明的装饰模式,有点儿像适配器模式的样子。
...
// 3
function addFly(canFly){
return function(target){
target.canFly = canFly;
let extra = canFly ? '(技能加成:飞行能力)' : '';
let method = target.prototype.toString;
target.prototype.toString = function (...args){
return method.apply(this, 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(技能加成:飞行能力)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
作用在方法上的 decorator
接收的第一个参数(target
)是类的 prototype
;如果把一个 decorator
作用到类上,则它的第一个参数 target
是 类本身。
# 使用原生 JS 实现装饰器模式
- Man 是具体的类,
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 经典实现:Logger
经典应用就是 日志系统 了,那么我们也用 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!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Logger 方法的关键在于:
- 首先使用
const method = descriptor.value;
将原有方法提取出来,保障原有方法的纯净; - 在
try..catch
语句是 调用ret = method.apply(target, args);
在调用之前之后分别进行日志汇报; - 最后返回
return ret;
原始的调用结果
# 相关库
- https://github.com/jayphelps/core-decorators
# vue中使用装饰器实现AOP编程
在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;
};
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
编辑我们的装饰器函数
//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!`);
});
};
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
编辑我们的vue
文件
//test.vue
<template>
<div></div>
</template>
<script>
import { before, after, around } from '@/lib/decorator';
export default {
data () {
},
methods: {
@before()
@after()
@around()
errorHandle () {
// 一些共用的异常处理方案
}
},
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.babelrc文件
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }]
]
}
2
3
4
5
# vue中 debounce 和 打点, logger 应用
// 理解JS函数节流和函数防抖 中节流 修改如下
export function debounce(wait = 800, now = true) {
let __timer = null;
let isFirst = now;
return function(target, key, descriptor) {
const method = descriptor.value;
descriptor.value = (...args) => {
if (__timer) clearTimeout(__timer);
if (isFirst) {
method.apply(target, args);
isFirst = false;
} else {
__timer = setTimeout(() => {
method.apply(target, args);
}, wait);
}
};
return descriptor;
};
}
export function goTrack(point){
return function (target, key, descriptor){
const method = descriptor.value
let ret = true;
descriptor.value = function (...args) {
ret = method.apply(this, args)
if (ret !== false){
// 条件 返回 false 不执行
console.warn('已经打点', point)
}
return ret;
}
return descriptor;
};
}
// 写一个 promise
export function pro(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 500);
});
}
export function logger(isAsync = false){
return function (target, key, descriptor){
const method = descriptor.value
let ret = true;
descriptor.value = function (...args) {
ret = method.apply(this, args)
if(isAsync){
// 是异步
ret.then(() => {
console.warn('logger success')
}).catch(() => {
console.error('logger fail')
})
}else {
console.warn('logger success')
}
return ret;
}
return descriptor;
};
}
// vue 中引入
{
methods: {
@debounce(1000, true)
outputFn (e) {
console.warn('输出', e.target.value)
},
@goTrack('#23856')
btnClick () {
if(!this.list.length){
return false;
}
console.log('开始打点')
},
@logger(true)
btnClick2 () {
return new Promise((resolve, reject) => {
pro().then(() => {
console.log('我是异步完成')
resolve()
})
});
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 摘自
- 淘宝前端团队在新窗口打开
- https://www.jianshu.com/p/208904b34d8f