zoukankan      html  css  js  c++  java
  • 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: 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模式

  • 相关阅读:
    【彩彩只能变身队】(迟到的)团队介绍
    【彩彩只能变身队】用户需求分析(二)—— 调查结果
    【彩彩只能变身队】用户需求分析(一)—— 调查问卷
    C语言I博客作业04
    C语言I博客作业06
    c语言1作业07
    C语言I博客作业03
    C语言I博客作业02
    C语言I博客作业05
    【OpenGL编程指南】之投影和视口变换
  • 原文地址:https://www.cnblogs.com/moyiqing/p/javascript_design.html
Copyright © 2011-2022 走看看