面向切面编程 AOP
面向切面编程 AOP(Aspect-oriented programming)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过「动态织入」的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。
通常,在 JavaScript 中实现 AOP,都是指把一个函数「动态织入」到另外一个函数之中,具体的实现技术有很多,下面的例子通过扩展 Function.prototype 配合高阶函数来做到这一点。
1 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
| Function.prototype.before = function(beforefn) { var __self = this; return function() { if (beforefn.apply(this, arguments) === false) { return false; } return __self.apply(this, arguments); } }; Function.prototype.after = function(afterfn) { var __self = this; return function() { var ret = __self.apply(this, arguments); if (ret === false) { return false; } afterfn.apply(this, arguments); return ret; } }; var func = function() { console.log(2); }; func = func.before(function() { console.log(1); }).after(function() { console.log(3); }); func();
|
无侵入的统计代码
利用上面的代码,我们可以将与业务逻辑无关的代码进行抽离,举个统计创建 1000 个 DOM 节点所需时间的例子:
1 2 3 4 5 6 7 8 9 10 11
| var append_doms = function () { var d = +new Date for (var i = 0; i < 1000; i++) { var div = document.createElement('div') document.body.appendChild(div) } user_log(+new Date - d, 'append_doms') }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var log_time = function (func, funcName) { return func = function() { var d return func.before(function() { d = +new Date }).after(function() { user_log(+new Date - d, funcName) }) } } var append_doms = function () { for (var i = 0; i < 1000; i++) { var div = document.createElement('div') document.body.appendChild(div) } } append_doms = log_time(append_doms, 'append_doms') append_doms()
|
分离表单请求和校验
我们在提交表单之前经常会做一些校验工作,来确定表单是不是应该正常提交,最糟糕的写法是把验证的逻辑都放在 send
函数里面,我们需要做的是分离它们:
1 2 3 4 5 6 7 8 9 10 11
| var send = function () { var value = input.value if (value.length === '') { return false } else if(value.length >= 30) { return false } else { form.submit() } }
|
下面是优化过的内容,我们把 validata
的内容给抽离了出来,但是在 send
方法中还是把「验证」和「发送」两件事情给耦合在了一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var validata_rules = { not_empty: function (value) { return value !== '' }, max_length: function (value) { return value.length > 30 } } var validata = function() { for (var i in validata_rules) { if (validata_rules[i].apply(this, arguments) === false) { return false } } } var send = function(value) { if (validata(value) === false) { return false } form.submit() }
|
更好的做法当然是 send
只做自己的事情,它只需要负责「发送」而不应该去管「验证」的事情,我们你用 AOP 来把它们给分离开来,只需要修改 send
方法:
1 2 3 4 5 6
| var send = function(value) { form.send() } send = send.before(validata)
|
职责链模式
职责链模式在 js 中典型的应用场景是事件冒泡。
将所有子节点和父节点连成一条链,并沿着这条链传递事件,直到有一个节点能够处理它为止。
职责链模式的目的就是消除过多的 if else
语句。
1 2 3 4 5 6 7 8 9 10
| if (support_plugin) { upload_obj = plugin } else if(support_html5) { upload_obj = html5 } else if(support_flash) { upload_obj = flash } else { upload_obj = form }
|
1 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
| Function.prototype.after = function(afterfn) { var __self = this; return function() { var ret = __self.apply(this, arguments); if (ret) { return ret; } afterfn.apply(this, arguments); return ret; } }; var get_plugin = function() { try { return new ActiveXObject('TXFTNActiveX.FTNUpload') } catch() { return null } } var get_html5 = function() { } var get_flash = function() { } var get_form = function() { } var upload_obj = get_plugin.after(get_html5).after(get_flash).after(get_form)
|
参考资料
JavaScript高阶函数的应用 - 个人文章 - SegmentFault 思否
用AOP改善javascript代码 | AlloyTeam
聊Javascript中的AOP编程 – 前端技术漫游指南