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

当我们面对比较复杂的前端项目时,我们经常会采用模块化的方式来对JavaScript代码进行解耦,以方便对代码的管理和维护,以下是一个简单的立即执行函数实现的模块化例子:
var Common = (function(){
    var func = function(){
        // 全局公用方法
    }
    return {
        func : func
    }
})();

var ModuleA = (function(){
    var _count = 1;
    var init = function(){
        // 独立模块逻辑
    }
    var getCount = function(){
        return _count;
    }
    return {
        init : init,
        getCount : getCount
    }
})();
模块只对外暴露外部需要的接口,而外部模块不需要关心其内部的运行逻辑,只需要知道调用接口的方式和返回结果,这样就实现了模块的“低耦合,高内聚”。

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

var Common = (function(){
    var func = function(){
        // 全局公用方法
        if(typeof ModuleA != 'undefined' && ModuleA.getCount() > 0){
            // 模块A需要进行的额外逻辑操作
        }
    }
    return {
        func : func
    }
})();

var ModuleA = (function(){
    var _count = 1;
    var init = function(){
        // 独立模块逻辑
    }
    var getCount = function(){
        return _count;
    }
    return {
        init : init,
        getCount : getCount
    }
})();
不知道当你看到Common.func中间的那一坨东西的时候,会不会突然怒吼一声:“卧槽,尼玛怎么这么恶心!”= =。。

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

var Common = (function(){
    var func = function(){
        // 全局公用方法
        if(typeof ModuleA != 'undefined' && ModuleA.getCount() > 0){
            // 模块A需要进行的额外逻辑操作
        }
        if(typeof ModuleB != 'undefined' && ModuleB.getWhat() != 'something'){
            // 模块B需要进行的额外逻辑操作
        }
        // ...
        if(typeof ModuleZ != 'undefined' && ModuleB.isWhat() !== false){
            // 模块Z需要进行的额外逻辑操作
        }
    }
    return {
        func : func
    }
})();
天哪,简直无法忍受。。

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

var Common = (function(){
    var func = function(){
        // 执行挂在这个方法的钩子上的所有额外逻辑代码
        Hook.doActions();
        // 全局公用方法
    }
    return {
        func : func
    }
})();

var ModuleA = (function(){
    var _count = 1;
    var init = function(){
        // 用钩子把额外的逻辑挂到Common.func上
        Hook.addAction(Common.func, function(){
            if(_count > 0){
                // 增加的额外逻辑操作
                console.log('看看执行到没?');
            }
        });
        // 独立模块逻辑
    }
    var getCount = function(){
        return _count;
    }
    return {
        init : init,
        getCount : getCount
    }
})();
没有不可能。借鉴了一下WordPress的Hook机制,一个基于JavaScript钩子机制模块就实现了。

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

/**
 * 钩子模块(动作是对方法的逻辑进行补充,过滤器是对方法的返回值进行处理)
 *
 * @author Lanfei
 * @date 2013.10.28
 * 
 * function handler(num1, num2){ 
 *     Hook.doActions();
 *     var value = num1 + num2;
 *     return Hook.applyFilters(value);
 * }
 * console.log('before hook:');
 * console.log(handler(1, 2));
 * function action(num1, num2){
 *     console.log('the numbers are ' + num1 + ' and ' + num2 + '.');
 * }
 * function filter(value){
 *     return 'the result is ' + value + '.';
 * }
 * Hook.addAction(handler, action);
 * Hook.addFilter(handler, filter);
 * console.log('after hook: ');
 * console.log(handler(1, 2));
 * 
 */

var Hook = (function(){

    var addAction = function(method, action, priority){
        _initHook(method);
        var actions = method['__hooks__'].actions;
        actions.push({
            action : action,
            priority : priority || 10
        });
        actions.sort(_compare);
    }

    var doActions = function(){
        var method = Hook.doActions.caller;
        _initHook(method);
        var actions = method['__hooks__'].actions;
        if(arguments.length == 0){
            arguments = method.arguments;
        }
        for(var i in actions){
            if(actions[i].action.apply(method, arguments) === false){
                return false;
            }
        }
    }

    var hasAction = function(method, action){
        _initHook(method);
        var actions = method['__hooks__'].actions;
        if(actions.length > 0 && action !== undefined){
            for(var i in actions){
                if(actions[i].action == action){
                    return true;
                }
            }
            return false;
        }else{
            return actions.length > 0;
        }
    }

    var removeAction = function(method, action){
        _initHook(method);
        var actions = method['__hooks__'].actions;
        if(actions.length > 0){
            if(action !== undefined){
                for(var i in actions){
                    if(actions[i].action == action){
                        delete actions[i];
                        return;
                    }
                }
            }else{
                method['__hooks__'].actions = [];
            }
        }
    }

    var addFilter = function(method, filter, priority){
        _initHook(method);
        var filters = method['__hooks__'].filters;
        filters.push({
            filter : filter,
            priority : priority || 10
        });
        filters.sort(_compare);
    }

    var applyFilters = function(value){
        var method = Hook.applyFilters.caller;
        _initHook(method);
        var filters = method['__hooks__'].filters;
        for(var i in filters){
            value = filters[i].filter.call(method, value);
        }
        return value;
    }

    var hasFilter = function(method, filter){
        _initHook(method);
        var filters = method['__hooks__'].filters;
        if(filters.length > 0 && filter !== undefined){
            for(var i in filters){
                if(filters[i].filter == filter){
                    return true;
                }
            }
            return false;
        }else{
            return filters.length > 0;
        }
    }

    var removeFilter = function(method, filter){
        _initHook(method);
        var filters = method['__hooks__'].filters;
        if(filters.length > 0){
            if(filter !== undefined){
                for(var i in filters){
                    if(filters[i].filter == filter){
                        delete filters[i];
                        return;
                    }
                }
            }else{
                method['__hooks__'].filters = [];
            }
        }
    }

    var _compare = function(hook1, hook2){
        return hook1.priority < hook2.priority;
    }

    var _initHook = function(method){
        if(! method['__hooks__']){
            method['__hooks__'] = {
                actions : [],
                filters : []
            };
        }
    }

    return {
        addAction : addAction,
        doActions : doActions,
        hasAction : hasAction,
        removeAction : removeAction,
        addFilter : addFilter,
        applyFilters : applyFilters,
        hasFilter : hasFilter,
        removeFilter : removeFilter
    };
})();
ps:原文地址:http://www.clanfei.com/2013/10/1730.html/comment-page-1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值