javascript设计模式之装饰者模式


Posted in Javascript onJanuary 30, 2020

在js函数开发中,想要为现有函数添加与现有功能无关的新功能时,按普通思路肯定是在现有函数中添加新功能的代码。这并不能说错,但因为函数中的这两块代码其实并无关联,后期维护成本会明显增大,也会造成函数臃肿。

比较好的办法就是采用装饰器模式。在保持现有函数及其内部代码实现不变的前提下,将新功能函数分离开来,然后将其通过与现有函数包装起来一起执行。

先来看个比较原始的js版装饰器模式实现:

var Plane = function(){}

Plane.prototype.fire = function(){
 console.log('发射普通子弹');
}

//增加两个装饰类,导弹类和原子弹类
var MissileDecorator = function(plane){
 this.plane = plane;
}
MissileDecorator.prototype.fire = function(){
 this.plane.fire();
 console.log('发射导弹');
}

var AtomDecorator = function(plane){
 this.plane = plane;
}
AtomDecorator.prototype.fire = function(){
 this.plane.fire();
 console.log('发射原子弹');
}

var plane = new Plane();
console.log(plane);
plane = new MissileDecorator(plane);
console.log(plane);
plane = new AtomDecorator(plane);
console.log(plane);

plane.fire();

/*
发射普通子弹
发射导弹
发射原子弹
*/

升级版装饰器模式,通过为js的Function构造函数添加实例方法before和after来实现。

Function.prototype.before和Function.prototype.after接收一个函数作为参数,这个函数就是新添加的函数,它装载了新添加的功能代码。

接下来把当前的this保存起来,这个this指向原函数(Function是js中所有函数的构造器,所以js中的函数都是Function的实例,Function.prototype中的this就指向该实例函数)

然后返回一个'代理'函数,这个代理函数只是结构上像'代理'而已,并不承担代理的职责(比如控制对象的访问)。它的工作就是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序,让新添加的函数在原函数之前执行(前置装饰 Function.prototype.before; 后置装饰 Function.prototype.after),从而实现动态装饰的效果。

// AOP 装饰函数
Function.prototype.before = function(beforefn){
 var _self = this;  //保存原函数的引用
 return function(){  //返回包含了原函数和新函数的‘代理'函数
  beforefn.apply(this, arguments);  //先执行新函数,且保证this不会被劫持,新函数接受的参数也会原封不动的传入原函数,新函数在原函数之前执行
  return _self.apply(this, arguments); //再执行原函数并返回原函数的执行结果,并保证this不被劫持
 }
}

Function.prototype.after = function(afterfn){
 var _self = this;  //保存原函数的引用
 return function(){  //返回包含了原函数和新函数的‘代理'函数
  var ret = _self.apply(this, arguments); //先执行原函数并返回原函数的执行结果,并保证this不被劫持,原函数执行的结果会赋值给ret变量,交由'代理'函数最后return
  afterfn.apply(this, arguments);   //再执行新函数,且保证this不会被劫持,新函数接受的参数也会原封不动的传入原函数,新函数在原函数之前执行  
  return ret; 
 }
}

//定义原函数
var print = function(){
 console.log('打印原函数执行结果');
}

print = print.before(function(){
 console.log('打印前置装饰函数的执行结果');
})

print = print.after(function(){
 console.log('打印后置装饰函数的执行结果');
})

//执行装饰后的print函数,为原函数print函数添加的装饰器对用户来说看起来是透明的
print();

//打印结果
/*
打印前置装饰函数的执行结果
打印原函数执行结果
打印后置装饰函数的执行结果
*/

上例中的AOP装饰器是通过在Function.prototype上添加before和after方法实现的,但有时这种直接污染函数原型的方法并不好,可以做些变通,把原函数和新函数都作为参数传入before和after方法中

var before = function(fn, beforefn){
 return function(){
  beforefn.apply(this, arguments)
  return fn.apply(this, arguments)
 }
}

var after = function(fn, agterfn){
 return function(){
  var ret = fn.apply(this, arguments)
  agterfn.apply(this, arguments)
  return ret;
 }
}

var a = function(){
 console.log('原函数执行结果');
}

a = before(a, function(){
 console.log('前置装饰函数执行结果');
})

a = after(a, function(){
 console.log('后置装饰函数执行结果');
})

a()
/*
前置装饰函数执行结果
原函数执行结果
后置装饰函数执行结果
*/

最后再来个装饰器模式的实例应用。

在实际开发中比较常见的需求是用户数据上报,一般会在项目开发差不多后,陆续有此类需求提出,但此时如果要在对应函数中添加数据上报功能代码时,就会改动原有函数,既麻烦又增加开发测试成本。此时最好的就是使用装饰器模式通过将上报函数装饰到原有函数上。

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>装饰者模式应用数据上报</title>
</head>
<body>
 <button type="button" id="btn">点击登录并上报数据</button>
</body>
<script>
var showDialog = function(){
 console.log('显示登录弹窗');
}

var log = function(){
 console.log('计数上报');
}

var after = function(fn, afterFn){
 return function(){
  var ret = fn.apply(this, arguments)
  afterFn.apply(this, arguments)
  return ret;
 }
}

showDialog = after(showDialog, log)

document.getElementById('btn').onclick = showDialog;

</script>
</html>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS修改iframe页面背景颜色的方法
Apr 01 Javascript
浅谈Javascript线程及定时机制
Jul 02 Javascript
JavaScript实现为input与textarea自定义hover,focus效果的方法
Aug 21 Javascript
angularjs 表单密码验证自定义指令实现代码
Oct 27 Javascript
js querySelector() 使用方法
Dec 21 Javascript
JavaScript限定范围拖拽及自定义滚动条应用(3)
May 17 Javascript
纯js实现动态时间显示
Sep 07 Javascript
iscroll.js滚动加载实例详解
Jul 18 Javascript
更优雅的微信小程序骨架屏实现详解
Aug 07 Javascript
layui动态绑定事件的方法
Sep 20 Javascript
纯JS实现五子棋游戏
May 28 Javascript
ES11新增的这9个新特性,你都掌握了吗
Oct 15 Javascript
原生js+ajax分页组件
Jan 30 #Javascript
javascript设计模式之迭代器模式
Jan 30 #Javascript
通过Kettle自定义jar包供javascript使用
Jan 29 #Javascript
用js限制网页只在微信浏览器中打开(或者只能手机端访问)
Dec 24 #Javascript
vue引用外部JS的两种种方法
Jan 28 #Javascript
Java Varargs 可变参数用法详解
Jan 28 #Javascript
关于引入vue.js 文件的知识点总结
Jan 28 #Javascript
You might like
dedecms 批量提取第一张图片最为缩略图的代码(文章+软件)
2009/10/29 PHP
弹出模态框modal的实现方法及实例
2017/09/19 PHP
php设计模式之备忘模式分析【星际争霸游戏案例】
2020/03/24 PHP
js常见表单应用技巧
2008/01/09 Javascript
Javascript 获取滚动条位置等信息的函数
2009/09/08 Javascript
JavaScript 对象成员的可见性说明
2009/10/16 Javascript
汉化英文版的Dreamweaver CS5并自动提示jquery
2010/11/25 Javascript
基于jquery扩展漂亮的下拉框可以二次修改
2013/11/19 Javascript
浅谈EasyUI中编辑treegrid的方法
2015/03/01 Javascript
js实现选中复选框文字变色的方法
2015/08/14 Javascript
基于pako.js实现gzip的压缩和解压功能示例
2017/06/13 Javascript
jQuery Jsonp跨域模拟搜索引擎
2017/06/17 jQuery
vue中子组件向父组件传递数据的实例代码(实现加减功能)
2018/04/20 Javascript
一些手写JavaScript常用的函数汇总
2019/04/16 Javascript
微信小程序Echarts图表组件使用方法详解
2019/06/25 Javascript
ES6 Symbol在对象中的作用实例分析
2020/06/06 Javascript
python局部赋值的规则
2013/03/07 Python
python图像处理之反色实现方法
2015/05/30 Python
深入解答关于Python的11道基本面试题
2017/04/01 Python
Python2.7环境Flask框架安装简明教程【已测试】
2018/07/13 Python
python读取ini配置文件过程示范
2019/12/23 Python
使用python计算三角形的斜边例子
2020/04/15 Python
Python try except异常捕获机制原理解析
2020/04/18 Python
GUESS盖尔斯法国官网:美国时尚品牌
2016/09/23 全球购物
巴西男士胡须和头发护理产品商店:Beard
2017/11/13 全球购物
Under Armour安德玛英国官网:美国高端运动科技品牌
2018/09/17 全球购物
Currentbody法国:健康与美容高科技产品
2020/08/16 全球购物
信号量和自旋锁的区别?如何选择使用?
2015/09/08 面试题
高校教师自荐信范文
2014/03/13 职场文书
公司联欢会策划方案
2014/05/19 职场文书
无毒社区工作方案
2014/05/23 职场文书
经销商会议开幕词
2016/03/04 职场文书
2016年感恩母亲节活动总结
2016/04/01 职场文书
2016年“9.22”世界无车日活动小结
2016/04/05 职场文书
24句精辟的现实社会语录,句句扎心,道尽人性
2019/08/29 职场文书
Android基于Fresco实现圆角和圆形图片
2022/04/01 Java/Android