首页 > 代码库 > javascript之设计

javascript之设计

javascript设计模式

一.初始化时分支

我们在写一些函数库的时候经常需要做一些兼容性方面的操作,会写一些相应的工具函数,例如下面的代码:

 

 1 var utils = { 2     addListener: function(el, type, fn) { 3         if (typeof window.addEventListener === ‘function‘) { 4             el.addEventListener(type, fn, false); 5         } else if (typeof document.attachEvent === ‘function‘) { 6             el.attachEvent(‘on‘ + type, fn); 7         } else { 8             el[‘on‘ + type] = fn; 9         }10     },11     removeListener: function(el, type, fn) {12         //...13     }14 }

 

上面的这段代码再运行时每次都会需要去判断是哪种浏览器的特征,因此显得效率会比较低下,我们需要一种方式去改善这种情况。

解决方案就是添加初始化的方式,什么时候初始化?当然是浏览器第一次加载的时候就根据浏览器特征加载相应的代码,当然为了暴露我们需要的接口,使用对象字面量的方式还是必不可少的。

首先罗列出我们的接口:

1 var utils = {2     addListener: null,3     removeListener: null4 }

 

因为代码是按顺序执行的,因此在下面我们需要对它进行接口实现,在实现中进行浏览器特性检查:

 1 if (typeof window.addEventListener === ‘function‘) { 2     utils.addListener = function(el, type, fn) { 3         el.addEventListener(type, fn, false); 4     }; 5     utils.removeListener = function(el, type, fn) { 6         //... 7     }; 8 } else if (typeof document.attachEvent === ‘function‘) { 9     utils.addListener = function(el, type, fn) {10         el.attachEvent(‘on‘ + type, fn);11     };12     utils.removeListener = function(el, type, fn) {13         //...14     };15 } else {16     utils.addListener = function(el, type, fn) {17         el[‘on‘ + type] = fn;18     };19     utils.removeListener = function(el, type, fn) {20         //...21     }22 }

 

这种方式很好的改善了效率低下的问题,而且也相当简单,只需要将重复性的代码从函数实现中分离出来,在外部实现即可。

二.函数属性

有些时候我们不得不应对一些计算量比较大或者耗时比较长的操作,比方说模糊查询,搜索功能等。减少重复性的工作是我们写代码的时候一直注重的一条准则,那么有什么办法能在这些耗时操作中能够尽量提高效率呢?其中一个方法便是缓存, 在javascript中,函数就是对象,是对象那就必然是有属性的,我们可以给任意一个函数添加任意的属性,比如:

1 var fn = function() {2     //todo3 };4 //添加属性5 fn.child = {};6 fn.add = function(){};

 

由此,我们可以给比较耗时的函数添加一个自身属性,以便缓存计算结果,缓存可以以任意类型的数据形式存在:

 

 1 //无参形式 2 var fn = function() { 3     var self = arguments.callee, result; 4     if (!self.cache) { 5         result = {}; 6         //耗时操作 7         self.cache = result; 8     } 9     return self.cache;10 }11 fn.cache = null;12 13 //有参形式14 var fn = function() {15     var key = JSON.stringify(Array.prototype.slice.call(arguments)), result;16     if (!arguments.callee.cache[key]) {17         result = {};18         //耗时操作19         fn.cache[key] = result;20     }21     return fn.cache[key];22 };23 fn.cache = {};

 

三.部分应用(curry化)

在这里我们考虑一种情况,有一个函数,需要传递多个参数去实现,而这个函数会在很多时候用到,很多时候我们会传递一些其中一些相同的参数,比如:

1 var fn = function(a, b, c, d) {2     //todo...3 };4 //调用5 fn(1, 2, 3, 4);6 fn(1, 2, 5, 6);7 fn(1, 2, 7, 8);8 fn(3, 4, 5, 6);

 

可以看到上面这个函数,我们进行了四次调用,而其有三次调用的前两个参数都是一样的,因此我们可以重新封装一个新的函数,以便减少重复性的参数输入。为了能够自动生成我们需要的函数,我们需要创建工具函数:

1 function curry(fn) {2     var slice = Array.prototype.slice, oldArgs = slice.call(arguments, 1);3     return function() {4         var newArgs = slice.call(arguments),5             args = oldArgs.concat(newArgs);6             return fn.apply(null, args);7     };8 }

该函数接受第一个参数为需要进行curry化的函数,后面为接受curry化的函数的部分或全部参数,使用如下:

1 var newFn = curry(fn, 1);2 newFn(2, 3, 4);3 4 var newFn2 = curry(fn, 2, 3);5 newFn2(5, 6);

 

四.命名空间

很多时候我们在做项目的时候,需要做一些对象管理,并且希望所创建出来的对象不会污染全局空间,因此我们引入了命名空间的概念,在javascript中,要实现命名空间,仅仅需要简单的实现一个对象即可,在对象内实现你所需要实现的任何操作。类似于下面的代码:

1 var Main = Main || {};

上面就是我们所需要的全局命名空间,剩下的工作,我们就是在Main的命名空间之内添加任意模块。当我们希望创建一个名为Main.utils.http这个模块的时候,我们会发现这时候需要做3次的检查来判断Main或者utils或者http这几个模块是否存在。这样做当然很不方面,所以我们又有了这样一个工具函数来帮助我们创建新模块:

 1 var Main = Main || {}; 2 Main.namespace = function(ns) { 3     var parts = ns.split(‘.‘), parent = Main, i; 4  5     if (parts[0] === ‘Main‘) { 6         parts = parts.slice(1); 7     } 8  9     for (i = 0; i < parts.length; i++) {10         if (typeof parent[parts[i]] === ‘undefined‘) {11             parent[parts[i]] = {};12         }13         parent = parent[parts[i]];14     }15     return parent;16 }17 18 //usage:19 var http = Main.namespace(‘Main.utils.http‘);20 var http = Main.namespace(‘utils.http‘);

 有了命名空间辅助工具函数,我们能够轻易的构建自己的常用工具库:

 1 Ken.namespace(‘utils.http‘); 2 Ken.utils.http = (function() { 3      4     var privatePropoty = ‘ken‘; 5  6     function ajax(option) { 7         //todo 8     } 9 10     function jsonp(option) {11         //todo12     }13     //...14     return {15         ajax: ajax,16         jsonp: jsonp17     }18 })();

 当然项目开发中,我们总是希望能够代码尽量模块化,所以我们可能会经常这样子做:

 1 var model = (function() { 2     var fn = function() {}; 3     fn.prototype = {}; 4     var a = 1; 5     function fn2 = function() {}; 6     return { 7         fn: fn, 8         fn2: fn2, 9         a: a10     };11 }());

这样能够比较好的封装了我们的模块,在多数时候这样简单的方式也足够我们用了,更进一步我们希望模块的组织结构能够像requirejs那样依赖加载,那将是非常好的一种实践。

在requirejs中,我们的代码组织就像下面这样:

1 require([‘jquery‘,‘ux/jquery.common‘],function($,common){2      var a = 1, b = 2;3      //todo...4 });

只需要准备好各个模块的代码,在需要用到的时候添加需要依赖的模块就可以了,这样做有一个好处,能够避免我们的依赖项变成对整个应用程序的全局变量依赖,而无法使用多版本相同模块的问题,当然这种情况极少,但能够解决这样的问题无疑是极好的, 我们能够用沙箱模式简单模拟出这种效果:

 1     Sandbox.modules = {}; 2     Sandbox.modules.dom = function(box) { 3         box.getElement = function() {}; 4         box.foo = "bar"; 5     }; 6     Sandbox.modules.event = function(box) { 7         box.attachEvent = function() {}; 8         box.dettachEvent = function() {}; 9     };10     function Sandbox() {11         var args = Array.prototype.slice.call(arguments),12             callback = args.pop(),13             modules = (args[0] && typeof args[0] === ‘String‘) ? args : args[0],14             i;15         if (!(this instanceof Sandbox)) {16             return new Sandbox(modules, callback);17         }18         if (!modules || modules === ‘*‘) {19             modules = [];20             for (i in Sandbox.modules) {21                 if (Sandbox.modules.hasOwnProperty(i)) {22                     modules.push(i);23                 }24             }25         }26         for (i = 0; i < modules.length; i+= 1) {27             Sandbox.modules[modules[i]](this);28         }29         callback(this);30     }31     Sandbox.prototype = {32         name: ‘ken‘,33         version: ‘1.0‘,34         getName: function() {35             return this.name;36         }37     }38 39     //usage:40     Sandbox(‘*‘, function(box) {41         console.log(box);42     });

这样做确实能帮我们实现类似requirejs那样的模块加载效果,但是相比于requirejs还缺少很多东西,对于模块的定义仍然不够轻便,在加载模块的时候我们可能需要一个一个标签的在页面中引入,但是在这个雏形中,我们能够不断去完善它并作为我们自己的工具来使用。

 

详细内容请参考-- javascript模式

javascript之设计