[设计模式]JavaScript钩子机制的应用场景与实现

当我们面对比较复杂的前端项目时,我们经常会采用模块化的方式来对JavaScript代码进行解耦,以方便对代码的管理和维护,以下是一个简单的立即执行函数实现的模块化例子:


 
 
  1. var Common = (function(){
  2.     var func = function(){
  3.         // 全局公用方法
  4.     }
  5.     return {
  6.         func : func
  7.     }
  8. })();
  9. var ModuleA = (function(){
  10.     var _count = 1;
  11.     var init = function(){
  12.         // 独立模块逻辑
  13.     }
  14.     var getCount = function(){
  15.         return _count;
  16.     }
  17.     return {
  18.         init : init,
  19.         getCount : getCount
  20.     }
  21. })();


模块只对外暴露外部需要的接口,而外部模块不需要关心其内部的运行逻辑,只需要知道调用接口的方式和返回结果,这样就实现了模块的“低耦合,高内聚”。


看起来很美好,可是当项目的逻辑变的越来越复杂时,比如A模块中某个针对全局公用的逻辑,可能在B模块的某种特定情况下需要进行一些额外的逻辑操作,该怎么办呢?


 
 
  1. var Common = (function(){
  2.     var func = function(){
  3.         // 全局公用方法
  4.         if(typeof ModuleA != 'undefined' && ModuleA.getCount() > 0){
  5.             // 模块A需要进行的额外逻辑操作
  6.         }
  7.     }
  8.     return {
  9.         func : func
  10.     }
  11. })();
  12. var ModuleA = (function(){
  13.     var _count = 1;
  14.     var init = function(){
  15.         // 独立模块逻辑
  16.     }
  17.     var getCount = function(){
  18.         return _count;
  19.     }
  20.     return {
  21.         init : init,
  22.         getCount : getCount
  23.     }
  24. })();


不知道当你看到Common.func中间的那一坨东西的时候,会不会突然怒吼一声:“卧槽,尼玛怎么这么恶心!”= =。。


明明是A模块的逻辑,却恶心地出现在了公用模块里,如果这样的特殊逻辑多起来之后,Common模块会不会变成这样?


 
 
  1. var Common = (function(){
  2.     var func = function(){
  3.         // 全局公用方法
  4.         if(typeof ModuleA != 'undefined' && ModuleA.getCount() > 0){
  5.             // 模块A需要进行的额外逻辑操作
  6.         }
  7.         if(typeof ModuleB != 'undefined' && ModuleB.getWhat() != 'something'){
  8.             // 模块B需要进行的额外逻辑操作
  9.         }
  10.         // ...
  11.         if(typeof ModuleZ != 'undefined' && ModuleB.isWhat() !== false){
  12.             // 模块Z需要进行的额外逻辑操作
  13.         }
  14.     }
  15.     return {
  16.         func : func
  17.     }
  18. })();


天哪,简直无法忍受。。


如果。。如果有这么一个钩子(Hook),可以把额外的逻辑代码挂在Common.func上,而Common.func执行的时候顺便把钩子上挂着的代码也执行了,那该多好啊。。这样的话既可以实现特殊的额外操作,又可以保持模块的低耦合和高内聚:


 
 
  1. var Common = (function(){
  2.     var func = function(){
  3.         // 执行挂在这个方法的钩子上的所有额外逻辑代码
  4.         Hook.doActions();
  5.         // 全局公用方法
  6.     }
  7.     return {
  8.         func : func
  9.     }
  10. })();
  11. var ModuleA = (function(){
  12.     var _count = 1;
  13.     var init = function(){
  14.         // 用钩子把额外的逻辑挂到Common.func上
  15.         Hook.addAction(Common.func, function(){
  16.             if(_count > 0){
  17.                 // 增加的额外逻辑操作
  18.                 console.log('看看执行到没?');
  19.             }
  20.         });
  21.         // 独立模块逻辑
  22.     }
  23.     var getCount = function(){
  24.         return _count;
  25.     }
  26.     return {
  27.         init : init,
  28.         getCount : getCount
  29.     }
  30. })();


没有不可能。借鉴了一下WordPress的Hook机制,一个基于JavaScript钩子机制模块就实现了。


当然,一个完整的钩子机制需要考虑的并不像上面说的那么简单,具体的实现大家请看代码,或者懒得看的可以自己尝试实现,我就不在赘述了:


 
 
  1. /**
  2.  * 钩子模块(动作是对方法的逻辑进行补充,过滤器是对方法的返回值进行处理)
  3.  *
  4.  * @author Lanfei
  5.  * @date 2013.10.28
  6.  * 
  7.  * function handler(num1, num2){ 
  8.  *     Hook.doActions();
  9.  *     var value = num1 + num2;
  10.  *     return Hook.applyFilters(value);
  11.  * }
  12.  * console.log('before hook:');
  13.  * console.log(handler(1, 2));
  14.  * function action(num1, num2){
  15.  *     console.log('the numbers are ' + num1 + ' and ' + num2 + '.');
  16.  * }
  17.  * function filter(value){
  18.  *     return 'the result is ' + value + '.';
  19.  * }
  20.  * Hook.addAction(handler, action);
  21.  * Hook.addFilter(handler, filter);
  22.  * console.log('after hook: ');
  23.  * console.log(handler(1, 2));
  24.  * 
  25.  */
  26. var Hook = (function(){
  27.     var addAction = function(method, action, priority){
  28.         _initHook(method);
  29.         var actions = method['__hooks__'].actions;
  30.         actions.push({
  31.             action : action,
  32.             priority : priority || 10
  33.         });
  34.         actions.sort(_compare);
  35.     }
  36.     var doActions = function(){
  37.         var method = Hook.doActions.caller;
  38.         _initHook(method);
  39.         var actions = method['__hooks__'].actions;
  40.         if(arguments.length == 0){
  41.             arguments = method.arguments;
  42.         }
  43.         for(var i in actions){
  44.             if(actions[i].action.apply(method, arguments) === false){
  45.                 return false;
  46.             }
  47.         }
  48.     }
  49.     var hasAction = function(method, action){
  50.         _initHook(method);
  51.         var actions = method['__hooks__'].actions;
  52.         if(actions.length > 0 && action !== undefined){
  53.             for(var i in actions){
  54.                 if(actions[i].action == action){
  55.                     return true;
  56.                 }
  57.             }
  58.             return false;
  59.         }else{
  60.             return actions.length > 0;
  61.         }
  62.     }
  63.     var removeAction = function(method, action){
  64.         _initHook(method);
  65.         var actions = method['__hooks__'].actions;
  66.         if(actions.length > 0){
  67.             if(action !== undefined){
  68.                 for(var i in actions){
  69.                     if(actions[i].action == action){
  70.                         delete actions[i];
  71.                         return;
  72.                     }
  73.                 }
  74.             }else{
  75.                 method['__hooks__'].actions = [];
  76.             }
  77.         }
  78.     }
  79.     var addFilter = function(method, filter, priority){
  80.         _initHook(method);
  81.         var filters = method['__hooks__'].filters;
  82.         filters.push({
  83.             filter : filter,
  84.             priority : priority || 10
  85.         });
  86.         filters.sort(_compare);
  87.     }
  88.     var applyFilters = function(value){
  89.         var method = Hook.applyFilters.caller;
  90.         _initHook(method);
  91.         var filters = method['__hooks__'].filters;
  92.         for(var i in filters){
  93.             value = filters[i].filter.call(method, value);
  94.         }
  95.         return value;
  96.     }
  97.     var hasFilter = function(method, filter){
  98.         _initHook(method);
  99.         var filters = method['__hooks__'].filters;
  100.         if(filters.length > 0 && filter !== undefined){
  101.             for(var i in filters){
  102.                 if(filters[i].filter == filter){
  103.                     return true;
  104.                 }
  105.             }
  106.             return false;
  107.         }else{
  108.             return filters.length > 0;
  109.         }
  110.     }
  111.     var removeFilter = function(method, filter){
  112.         _initHook(method);
  113.         var filters = method['__hooks__'].filters;
  114.         if(filters.length > 0){
  115.             if(filter !== undefined){
  116.                 for(var i in filters){
  117.                     if(filters[i].filter == filter){
  118.                         delete filters[i];
  119.                         return;
  120.                     }
  121.                 }
  122.             }else{
  123.                 method['__hooks__'].filters = [];
  124.             }
  125.         }
  126.     }
  127.     var _compare = function(hook1, hook2){
  128.         return hook1.priority < hook2.priority;
  129.     }
  130.     var _initHook = function(method){
  131.         if(! method['__hooks__']){
  132.             method['__hooks__'] = {
  133.                 actions : [],
  134.                 filters : []
  135.             };
  136.         }
  137.     }
  138.     return {
  139.         addAction : addAction,
  140.         doActions : doActions,
  141.         hasAction : hasAction,
  142.         removeAction : removeAction,
  143.         addFilter : addFilter,
  144.         applyFilters : applyFilters,
  145.         hasFilter : hasFilter,
  146.         removeFilter : removeFilter
  147.     };
  148. })();


PS:发现我写博客不怎么喜欢用图片,不知道大家看起来会不会不太喜欢= =。。



=======================签 名 档=======================
原文地址(我的博客):http://www.clanfei.com/2013/10/1730.html
欢迎访问交流,至于我为什么要多弄一个博客,因为我热爱前端,热爱网页,我更希望有一个更加自由、真正属于我自己的小站,或许并不是那么有名气,但至少能够让我为了它而加倍努力。。
=======================签 名 档=======================


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值