代码改变世界

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模式