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 相关文章推荐
Mootools 1.2教程 设置和获取样式表属性
Sep 15 Javascript
利用jquery的获取JS文件中的字符串内容
Feb 14 Javascript
js一般方法改写成面向对象方法的无限级折叠菜单示例代码
Jul 04 Javascript
文本框倒叙输入让输入框的焦点始终在最开始的位置
Sep 01 Javascript
快速掌握Node.js环境的安装与运行方法
Feb 16 Javascript
JavaScript循环_动力节点Java学院整理
Jun 28 Javascript
基于jQuery的$.getScript方法去加载javaScript文档解析
Nov 08 jQuery
vue-resource请求实现http登录拦截或者路由拦截的方法
Jul 11 Javascript
详解bootstrap-fileinput文件上传控件的亲身实践
Mar 21 Javascript
React中Ref 的使用方法详解
Apr 28 Javascript
基于javascript原生判断DOM是否加载完毕
Oct 14 Javascript
使用Vant完成DatetimePicker 日期的选择器操作
Nov 12 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同时连接多个mysql数据库示例代码
2014/03/17 PHP
PHP 用session与gd库实现简单验证码生成与验证的类方法
2016/11/15 PHP
golang实现php里的serialize()和unserialize()序列和反序列方法详解
2018/10/30 PHP
ExtJS 2.0 实用简明教程之布局概述
2009/04/29 Javascript
JavaScript 学习笔记二 字符串拼接
2010/03/28 Javascript
Javascript变量函数浅析
2011/09/02 Javascript
你必须知道的JavaScript 中字符串连接的性能的一些问题
2013/05/07 Javascript
Bootstrap每天必学之按钮(一)
2015/11/24 Javascript
WordPress中利用AJAX技术进行评论提交的实现示例
2016/01/12 Javascript
javascript 动态脚本添加的简单方法
2016/10/11 Javascript
JavaScript实现短暂提示框功能
2018/04/04 Javascript
vue中的自定义分页插件组件的示例
2018/08/18 Javascript
ES6入门教程之let、const的使用方法
2019/04/13 Javascript
浅谈TypeScript 用 Webpack/ts-node 运行的配置记录
2019/10/11 Javascript
解决vuex数据异步造成初始化的时候没值报错问题
2019/11/13 Javascript
如何构建一个Vue插件并生成npm包
2020/10/26 Javascript
python实现ftp客户端示例分享
2014/02/17 Python
python分析网页上所有超链接的方法
2015/05/08 Python
浅谈python中的面向对象和类的基本语法
2016/06/13 Python
详解python实现线程安全的单例模式
2018/03/05 Python
pygame游戏之旅 按钮上添加文字的方法
2018/11/21 Python
解决python3 HTMLTestRunner测试报告中文乱码的问题
2018/12/17 Python
python实现顺时针打印矩阵
2019/03/02 Python
ML神器:sklearn的快速使用及入门
2019/07/11 Python
Python数据持久化存储实现方法分析
2019/12/21 Python
Python可以实现栈的结构吗
2020/05/27 Python
Python性能分析工具py-spy原理用法解析
2020/07/27 Python
一个J2EE项目团队的主要人员组成是什么
2012/06/04 面试题
师范大学应届生求职信
2013/11/21 职场文书
出纳岗位职责范本
2013/12/01 职场文书
雷锋式好少年事迹材料
2014/08/17 职场文书
纪念一二九运动演讲稿
2014/09/16 职场文书
同学聚会开幕词
2019/04/02 职场文书
Python爬虫基础之爬虫的分类知识总结
2021/05/13 Python
JavaWeb Servlet实现网页登录功能
2021/07/04 Java/Android
科学家研发出新型速效酶,可在 24 小时内降解塑料制品
2022/04/29 数码科技