javascript之设计
2014-09-08 11:15 king0222 阅读(164) 评论(0) 编辑 收藏 举报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: null 4 }
因为代码是按顺序执行的,因此在下面我们需要对它进行接口实现,在实现中进行浏览器特性检查:
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 //todo 3 }; 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 //todo 12 } 13 //... 14 return { 15 ajax: ajax, 16 jsonp: jsonp 17 } 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: a 10 }; 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模式