本文是《JavaScript设计模式与开发实践》的学习笔记,例子来源于书中,对于设计模式的见解,推荐看看本书做者的建议。html
给对象动态增长职责的方式成为装饰者模式。ajax
装饰者模式可以在不改变对象自身的基础上,在运行程序期间给对象动态地添加职责。这是一种轻便灵活的作法,装饰者是一种“即付即用”的方式,好比天冷了就多穿一件外套。编程
想要为函数添加一些功能,最简单粗暴的方式就是直接改写该函数,可是这是最差的办法,直接违反了开放——封闭原则。设计模式
var a = function(){ alert(1) } // 改为 var a = function(){ alert(1) alert(2) }
不少时候咱们不想碰原函数,也许原函数是其余同事编写的,甚至在一个古老的项目中,这个函数的源代码被隐藏在一个咱们不肯触碰的阴暗角落里。如今须要不改变源代码的状况下,给函数增长功能。安全
咱们经过保存原引用的方式改写某个函数。app
var a = function(){ alert(1) } var _a = a a = function(){ _a() alert(2) } a()
这是实际开发中很常见的一个作法,好比咱们想给 window 绑定 onload 事件,可是又不肯定这个事件是否是已经被其余人绑定过,为了不覆盖掉以前的 window.onload 函数中的行为,先保存 window.onload,把它放入新的 window.onload。函数
window.onload = function(){ alert(1) } var _onload = window.onload || function(){} window.onload = funtion(){ _onload() alert(2) }
这样的代码是符合封闭——开放原则,咱们在增长新功能的时候确实没有修改原来的代码,可是这种方式存在两个问题:学习
AOP(Aspect Oriented Programming)面向切面编程的主要做用是:把一些跟核心业务逻辑无关的功能抽离出来,这些跟业务逻辑无关的功能一般包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再经过“动态织入”的方式掺入业务逻辑模块中。这样的好处首先是能够保持业务逻辑模块的纯净和高内聚性,其次是能够很方便地复用日志统计等功能模块。优化
首先给出 Function.prototype.before 和 Function.prototype.after 方法:this
Function.prototype.before = function(beforefn){ // 保存原函数的引用 var _self = this // 返回包含原函数和新函数的“代理函数” return function(){ // 执行新函数,保证this不被劫持, // 新函数接受的参数也会被原封不动地传入原函数, // 新函数在原函数以前执行 beforefn.apply(this,arguments) return _self.apply(this.arguments) } } Function.prototype.after = function(afterfn){ var _self = this return function(){ var ret = _self.apply(this,arguments) afterfn.apply(this,arguments) return ret } }
“代理函数”只是结构上像代理而已,并不承担代理的职责(好比控制对象的访问),它的工做是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序。
再回到 window.onload 的例子中,用 Function.prototype.after 来增长新事件:
window.onload = function(){ alert(1) } window.onload = (window.onload || function(){}).after(function(){ alert(2) }).after(function(){ alert(3) })
(1)数据统计上报
<html> <button tag="login" id="button">点击打开登陆浮层</button> <script> var showLogin = function(){ console.log( '打开登陆浮层' ) log( this.getAttribute( 'tag' ) ) } var log = function( tag ){ console.log( '上报标签为: ' + tag ) } document.getElementById( 'button' ).onclick = showLogin </script> </html>
showLogin 函数既要负责打开浮层,又要负责数据上报,两个功能耦合在一个函数里,使用 AOP 分离:
<html> <button tag="login" id="button">点击打开登陆浮层</button> <script> Function.prototype.after = function( afterfn ){ var __self = this; return function(){ var ret = __self.apply( this, arguments ) afterfn.apply( this, arguments ) return ret } } var showLogin = function(){ console.log( '打开登陆浮层' ) } var log = function(){ console.log( '上报标签为: ' + this.getAttribute( 'tag' ) ) } showLogin = showLogin.after( log ); // 打开登陆浮层以后上报数据 document.getElementById( 'button' ).onclick = showLogin; </script> </html>
(2) 插件式表单验证
<html> <body> 用户名:<input id="username" type="text"/> 密码: <input id="password" type="password"/> <input id="submitBtn" type="button" value="提交"></button> </body> <script> var username = document.getElementById( 'username' ), password = document.getElementById( 'password' ), submitBtn = document.getElementById( 'submitBtn' ); var formSubmit = function(){ if ( username.value === '' ){ return alert ( '用户名不能为空' ); } if ( password.value === '' ){ return alert ( '密码不能为空' ); } var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); // ajax 具体实现略 } submitBtn.onclick = function(){ formSubmit(); } </script> </html>
formatSubmit 函数承担了两个职责,除了提交ajax请求,还要验证用户输入的合法性。咱们把校验输入的逻辑放到validata函数中,并约定当validata函数返回false的时候表示校验未经过。
var validata = function(){ if ( username.value === '' ){ alert ( '用户名不能为空' ); return false; } if ( password.value === '' ){ alert ( '密码不能为空' ); return false; } } var formSubmit = function(){ if ( validata() === false ){ // 校验未经过 return; } var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); } submitBtn.onclick = function(){ formSubmit(); }
使用AOP优化代码
Function.prototype.before = function( beforefn ){ var __self = this; return function(){ if ( beforefn.apply( this, arguments ) === false ){ // beforefn 返回false 的状况直接return,再也不执行后面的原函数 return; } return __self.apply( this, arguments ); } } var validata = function(){ if ( username.value === '' ){ alert ( '用户名不能为空' ); return false; } if ( password.value === '' ){ alert ( '密码不能为空' ); return false; } } var formSubmit = function(){ var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); } formSubmit = formSubmit.before( validata ); submitBtn.onclick = function(){ formSubmit(); }