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 相关文章推荐
JavaScript将Table导出到Excel实现思路及代码
Mar 13 Javascript
IE网页js语法错误2行字符1、FF中正常的解决方法
Sep 09 Javascript
解决window.opener=null;window.close(),只支持IE6不支持IE7,IE8的问题
Jan 14 Javascript
Angular用来控制元素的展示与否的原生指令介绍
Jan 07 Javascript
使用AngularJS实现表单向导的方法
Jun 19 Javascript
jQuery实现的无限级下拉菜单功能示例
Sep 12 Javascript
微信小程序 本地存储及登录页面处理实例详解
Jan 11 Javascript
laydate日历控件使用方法详解
Nov 20 Javascript
vue组件中iview的modal组件爬坑问题之modal的显示与否应该是使用v-show
Apr 12 Javascript
微信小程序从注册账号到上架(图文详解)
Jul 17 Javascript
vue项目初始化到登录login页面的示例
Oct 31 Javascript
JS实现图片懒加载(lazyload)过程详解
Apr 02 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
基于php 随机数的深入理解
2013/06/05 PHP
PHP中单引号与双引号的区别分析
2014/08/19 PHP
php自定义错误处理用法实例
2015/03/20 PHP
关于PHP中interface的用处详解
2020/07/26 PHP
判断是否输入完毕再激活提交按钮
2006/06/26 Javascript
用javascript实现给出的盒子的序列是否可连为一矩型
2007/08/30 Javascript
JavaScript asp.net 获取当前超链接中的文本
2009/04/14 Javascript
jQuery 表单验证扩展(四)
2010/10/20 Javascript
JavaScript获取并更改input标签name属性的方法
2015/07/02 Javascript
jQuery.prop() 使用详解
2015/07/19 Javascript
Google 地图类型详解及示例代码
2016/08/06 Javascript
实时监控input框,实现输入框与下拉框联动的实例
2018/01/23 Javascript
jquery ajaxfileuplod 上传文件 essyui laoding 效果【防止重复上传文件】
2018/05/26 jQuery
微信小程序WebSocket实现聊天对话功能
2018/07/06 Javascript
解决antd Form 表单校验方法无响应的问题
2020/10/27 Javascript
python实现问号表达式(?)的方法
2013/11/27 Python
python base64 decode incorrect padding错误解决方法
2015/01/08 Python
Python中使用PIL库实现图片高斯模糊实例
2015/02/08 Python
在PyCharm导航区中打开多个Project的关闭方法
2019/01/17 Python
selenium+PhantomJS爬取豆瓣读书
2019/08/26 Python
基于python的BP神经网络及异或实现过程解析
2019/09/30 Python
深入浅析python的第三方库pandas
2020/02/13 Python
python怎么判断素数
2020/07/01 Python
python将字典内容写入json文件的实例代码
2020/08/12 Python
Django admin组件的使用
2020/10/24 Python
Django框架请求生命周期实现原理
2020/11/13 Python
请说出以下代码输出什么
2013/08/30 面试题
供货协议书范本
2014/04/22 职场文书
加强干部作风建设整改方案
2014/10/24 职场文书
2015年仓库工作总结
2015/04/09 职场文书
2015年学校德育工作总结
2015/04/22 职场文书
毕业欢送晚会主持词
2019/06/25 职场文书
总结Pyinstaller打包的高级用法
2021/06/28 Python
ElementUI实现el-form表单重置功能按钮
2021/07/21 Javascript
Python编写冷笑话生成器
2022/04/20 Python
Mybatis-Plus 使用 @TableField 自动填充日期
2022/04/26 Java/Android