[设计模式]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
欢迎访问交流,至于我为什么要多弄一个博客,因为我热爱前端,热爱网页,我更希望有一个更加自由、真正属于我自己的小站,或许并不是那么有名气,但至少能够让我为了它而加倍努力。。
=======================签 名 档=======================