对jquery的ajax进行二次封装以及ajax缓存代理组件:AjaxCache详解


Posted in Javascript onApril 11, 2016

虽然jquery的较新的api已经很好用了, 但是在实际工作还是有做二次封装的必要,好处有:1,二次封装后的API更加简洁,更符合个人的使用习惯;2,可以对ajax操作做一些统一处理,比如追加随机数或其它参数。同时在工作中,我们还会发现,有一些ajax请求的数据,对实时性要求不高,即使我们把第一次请求到的这些数据缓存起来,然后当相同请求再次发起时直接拿之前缓存的数据返回也不会对相关功能有影响,通过这种手工的缓存控制,减少了ajax请求,多多少少也能帮助我们提高网页的性能。本文介绍我自己关于这两方面问题的做法,欢迎交流和指正。

点击 代码下载 (注:因为用到了ajax,所以不能在file协议下运行,必须运行在http下)

1. 封装jquery的ajax

其实这个部分的相关内容在之前的一篇博客引入过,只不过那里面只是引用,没有详细说明,另外ajax缓存代理组件的实现也是基于这个二次封装之后的ajax组件的,所以有必要在这里对它详细说明一下,虽然实现并不复杂(详见注释说明):

define(function (require, exports, module) {

  var $ = require('jquery');

  //根据关键的几个参数统一创建ajax对象
  function create(_url, _method, _data, _async, _dataType) {
    //添加随机数
    if (_url.indexOf('?') > -1) {
      _url = _url + '&rnd=' + Math.random();
    } else {
      _url = _url + '?rnd=' + Math.random();
    }

    //为请求添加ajax标识,方便后台区分ajax和非ajax请求
    _url += '&_ajax=1';

    //返回jquery创建的ajax对象,以便外部拿到这个对象以后可以通过
    //.done .fail .always来添加回调
    //这么做是为了保留jquery ajax中好用的部分
    return $.ajax({
      url: _url,
      dataType: _dataType,
      async: _async,
      method: _method,
      data: _data
    });
  }

  //ajax就是本组件全局唯一的实例,它的实例方法通过后面的循环代码添加
  //methods对象配置ajax各个实例方法的参数:
  //name: 方法名称
  //method: http请求方法,get or post
  //async: 发送请求时是否异步
  //dataType: 返回的数据类型,html or json
  var ajax = {},
    methods = [
      {
        name: 'html',
        method: 'get',
        async: true,
        dataType: 'html'
      },
      {
        name: 'get',
        method: 'get',
        async: true,
        dataType: 'json'
      },
      {
        name: 'post',
        method: 'post',
        async: true,
        dataType: 'json'
      },
      {
        name: 'syncGet',
        method: 'get',
        async: false,
        dataType: 'json'
      },
      {
        name: 'syncPost',
        method: 'post',
        async: false,
        dataType: 'json'
      }
    ];

  //由于二次封装需要对外提供的每个实例方法创建ajax的逻辑是相同的
  //所以通过这种方式统一定义各个实例方法
  //关键代码为下面代码中的那个立即调用的函数
  //它返回了一个新的闭包函数作为实例方法
  for (var i = 0, l = methods.length; i < l; i++) {
    ajax[methods[i].name] = (function (i) {
      return function () {
        /**
         * 每个实例方法接收三个参数
         * 第一个表示要请求的地址
         * 第二个表示要提交到后台的数据,是一个object对象,如{param1: 'value1'}
         * 第三个表示后台返回的数据类型,最最常用的就是html or json,绝大部分情况下这个参数不用传,会使用methods里面定义的dataType
         */
        var _url = arguments[0],
          _data = arguments[1],
          _dataType = arguments[2] || methods[i].dataType;

        return create(_url, methods[i].method, _data, methods[i].async, _dataType);
      }
    })(i);
  }

  return ajax;
});
这个Ajax组件主要做的事情是:
1)统一提供随机数参数和ajax请求标识;
2)对jquery的api进行了包装,对外提供的方法更加清晰明了。

使用方式:

define(function (require, exports, module) {

  var Ajax = require('mod/ajax');

  //以GET方式请求html内容
  Ajax.html('html/demo', {
    param1: 'value1',
    param2: 'value2'
  }).done(function(response){
    //请求成功的回调
  }).fail(function(){
    //请求失败的回调
  }).always(function(){
    //请求完成的回调
  });

  //以GET方式请求json数据
  Ajax.get('api/demo', {
    param1: 'value1',
    param2: 'value2'
  }).done(function(response){
    //请求成功的回调
  }).fail(function(){
    //请求失败的回调
  }).always(function(){
    //请求完成的回调
  });

  //以POST方式请求json数据
  Ajax.post('api/demo', {
    param1: 'value1',
    param2: 'value2'
  }).done(function(response){
    //请求成功的回调
  }).fail(function(){
    //请求失败的回调
  }).always(function(){
    //请求完成的回调
  });

  //以GET方式发送同步请求,获取json数据
  Ajax.syncGet('api/demo', {
    param1: 'value1',
    param2: 'value2'
  }).done(function(response){
    //请求成功的回调
  }).fail(function(){
    //请求失败的回调
  }).always(function(){
    //请求完成的回调
  });

  //以POST方式发送同步请求,获取json数据
  Ajax.syncPost('api/demo', {
    param1: 'value1',
    param2: 'value2'
  }).done(function(response){
    //请求成功的回调
  }).fail(function(){
    //请求失败的回调
  }).always(function(){
    //请求完成的回调
  });

});

由于这个组件的每个实例方法返回的对象就是$.ajax创建的对象,所以我们完全可以照常使用.done .fail .always来添加回调,就跟直接用$.ajax没有任何区别。为什么API要设计成html, get, post, syncGet, syncPost这几个方法,而且连dataType基本都是固定的?

那是因为在项目中,我们完全可以约定在异步请求的时候只能用html或json这两种dataType,其它dataType不允许,现在的web项目这两种方式已经完全够用了,至少我没有碰到过非得使用别的dataType不可的情况;而且在实际工作当中html, get, post, syncGet, syncPost这几个方法几乎能够涵盖我们需要的所有异步请求的场景,每当我们要用ajax的时候,无非考虑的就是get还是post,同步还是异步,请求的是html还是json这三个问题,通过它们就能把每个问题都解决了。当然jsonp,rest API这两种情况就另说了,这个组件不是为它们服务的,这也是它的局限性,它还是倾向于在传统的web项目中使用。

2. ajax缓存代理

要实现一个简单的ajax缓存代理组件,首先要清楚这个缓存代理的作用,在本文开篇说到过缓存代理的应用场景:当使用缓存代理第一个发起某个请求时,在请求成功后将数据缓存下来,然后当再次发起相同请求时直接返回之前缓存的数据,缓存代理的作用是控制何时发送请求去后台加载数据,何时不发送请求直接从缓存中读取之前加载的数据。为了实现一个简单的缓存代理,有三个问题要解决:

1)代理对象必须与被代理的对象有相同的API
拿前面的Ajax组件来说,它提供有html, get , post, syncGet, syncPost方法,那么它的代理对象也必须同时具有这些方法,而且调用方式,传入参数都必须完全一致,只有这样,当我们在使用代理对象的时候,就跟在使用原组件对象没有区别。而且在缓存代理内部,在某些条件下是需要调用原组件对象发送ajax请求的,如果接口不同,调用方式不同,参数不同,如何能保证内部能够正确调用原组件对象呢?这个条件还有一个好处,就是当我们下次不想使用代理对象的时候,能够以最小的代价将代理对象替换为原组件对象。
这一点其实是设计模式中代理模式的基本要求。

2)缓存数据存储时的缓存索引问题
也就是说我们以什么样的索引才能保证同一个请求的数据在缓存之后,下次查找时还能根据请求信息查找到呢?ajax缓存有别于其它缓存的地方在于它请求的地址可能包含可变的参数值,同一个地址如果后面的参数不同,那么对应的请求结果也就不一定相同,所以简单起见,可以考虑把请求地址跟请求参数统一作为缓存索引,这样就能对缓存进行简单管理。同时考虑到其它可变性,还应有其它的一些要求,详见后面组件实现中的注释说明。

3)缓存有效时间
虽然要实现的缓存代理很简单,但是这个问题一定是要考虑的,每个缓存代理实例,能够缓存数据的有效时间不一定相同,有的可能只缓存几分钟,有的可能缓存几十分钟,当缓存时间失效时,缓存代理就得删除原来的缓存,然后重新去加载数据才行。

综合这些问题,基于第一部分的Ajax组件,最终实现的缓存代理组件AjaxCache的代码如下(有注释详解):

define(function (require, exports, module) {

  var $ = require('jquery');
  var Ajax = require('mod/ajax');

  //缓存列表
  var cache = {};

  /**
   * 生成缓存索引:
   * 由于索引是根据url和data生成的(data是一个对象,存放Ajax要提交到后台的数据)
   * 所以要想同一个url,同样的data能够有效地使用缓存,
   * 切勿在url和data中包含每次可变的参数值,如随机数等
   * 比如有一个请求:
   * url: aaa/bbb/cccc?r=0.312738
   * data: {name: 'json'}
   * 其中url后面的r是一个随机数,每次外部发起这个请求时,r的值都会变化
   * 由于r每次都不同,最终会导致缓存索引不相同,结果缓存就无法命中
   * 注:随机数可放置在原始的Ajax组件内
   *
   * 还有:如果是同一个接口,最好在同一个页面内,统一url的路径类型,要么都是相对路径,要么都是绝对路径
   * 否则也会导致缓存无法有效管理
   */
  function generateCacheKey(url, data) {
    return url + $.param(data);
  }

  return function (opts) {
    opts = opts || {};

    var cacheInterval = opts.cacheInterval || (1000 * 60 * 60);//缓存有效时间,默认60分钟

    var proxy = {};
    for (var i in Ajax) {
      if (Object.prototype.hasOwnProperty.call(Ajax, i)) {
        //在proxy对象上定义Ajax组件每一个实例方法的代理
        //注意这个立即调用的函数表达式
        //它返回了一个闭包函数就是最终的代理方法
        proxy[i] = (function (i) {
          return function () {
            var _url = arguments[0],
              _data = arguments[1],
              cacheKey = generateCacheKey(_url, _data),
              cacheItem = cache[cacheKey],
              isCacheValid = false;

            if (cacheItem) {
              var curTime = +new Date();
              if (curTime - cacheItem.cacheStartTime <= cacheInterval) {
                //如果请求时间跟缓存开始时间的间隔在缓存有效时间范围内,就表示缓存是有效的
                isCacheValid = true;
              } else {
                //否则就把缓存清掉
                delete cache[cacheKey];
              }
            }

            if (isCacheValid) {
              //模拟一个异步任务来返回已经缓存的数据
              //通过$defer延迟对象,可以保证这个模拟任务返回的对象跟原始Ajax组件调用返回的对象有相同的API
              //这是代理的关键:代理对象与被代理的对象应该具有相同API
              //只有这样当我们取消代理的时候,不会对那些用了代理的组件进行修改
              var $defer = $.Deferred();
              setTimeout(function () {
                $defer.resolve(cacheItem.res);
              }, 10);
              return $.when($defer);
            }

            //缓存失效或者没有缓存的时候调用原始的Ajax组件的同名方法去后台请求数据
            return Ajax[i].apply(Ajax, arguments).done(function (res) {
              //在请求成功之后将结果缓存,并记录当前时间作为缓存的开始时间
              cache[cacheKey] = {
                res: res,
                cacheStartTime: +new Date()
              }
            });
          }
        })(i);
      }
    }
    return proxy;
  };
});
在第一部分和本部分的实现中,最关键的都是那个立即调用的函数表达式,没有它返回的闭包,代码就会有问题,这也是闭包在循环中应用的经典问题。

3. 演示效果

为了说明缓存代理的使用效果,我做了一个演示效果:
对jquery的ajax进行二次封装以及ajax缓存代理组件:AjaxCache详解 
其中的ajax.js就是第一部分的实现,ajaxCache.js就是第二部分的实现,演示页面对应代码中的html/demo.html,相关js是js/app/demo.js:

define(function (require, exports, module) {

  var AjaxCache = require('mod/ajaxCache');

  //创建代理对象
  var Ajax = new AjaxCache({
    cacheInterval: 10 * 1000
  });

  var count = 5;

  console.log('时间点:第' + 0 + 's,定时器开始!');
  var t = setInterval(function(){
    if(count == 0) {
      console.log('时间点:第' + (5 - count + 1) * 4 + 's,定时器结束!');
      return clearInterval(t);
    } else{
      console.log('时间点:第' + (5 - count + 1) * 4 + 's:');
    }

    Ajax.get('../api/data.json', {
      name: 'felix'
    }).done(function(res){
      if(res.code == 200) {
        console.log(5 - count + '. data is : ' + JSON.stringify(res.data));
      }
    });

    count --;
  },4000);

});

在这个代码中,我创建了一个代理对象,能将ajax请求缓存10s,用一个定时器一共调用代理对象五次get方法来发送同一个请求,最终打印效果如下:
对jquery的ajax进行二次封装以及ajax缓存代理组件:AjaxCache详解
从结果来看,整个代码执行了24s,代理发送请求的时间点分别是第4s,第8s,第12s,第16s和第20s。由于代理的缓存有效时间是10s,且第4s是第一次发送请求,所以此时肯定会发送真实的ajax请求;当第8s和第12s的代理发送同一请求时,由于距离缓存的时间只过去了4s和8s,所以缓存还是有效的,这两个时间点都没有发送实际的ajax请求;但是当第16s的请求发送时,距离第一次请求的缓存时间已经过去12s,缓存已经失效,所以代理又发送了一次真实的ajax请求,然后缓存被刷新;第20s的请求还是在最新的缓存有效时间内,所以也没有发送实际的ajax请求。最后在network中可以看到代理发送了5次请求,但是只请求了2次服务,如果把缓存有效时间延长,就能再减少请求后台的次数,这也是缓存代理对前端性能提升的关键。

希望这个演示效果能够让你更加清楚地了解缓存代理的作用。

4. 本文总结

本文第1部分总结的实现在我自己的工作中应用很多,至少没碰到什么问题,不过也有可能是我没遇到,毕竟那个组件实现还是有不少约束的。第2部分的实现我也是刚刚应用到工作中去,正好有一个功能我考虑到有缓存的必要性,于是就写了一个较为简单的实现,虽然简单,但是已经能解决我的问题了,实际工作本来就是这样,有些东西没必要事无巨细的在事前就设计地很完美,先解决问题,然后在遇到新问题的时候再回来重构,有时也是一种更好的工作方法。下一篇博客介绍另外一个用到缓存代理的组件的实现思路,跟省市级联类似的功能,不过我想的是写成通用性更强的,能够与html结构和css尽可能分离的组件,请您继续关注。

以上这篇对jquery的ajax进行二次封装以及ajax缓存代理组件:AjaxCache详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于jquery的bankInput银行卡账号格式化
Aug 22 Javascript
js和jquery中循环的退出和继续下一个循环
Sep 03 Javascript
js实现的黑背景灰色二级导航菜单效果代码
Aug 24 Javascript
javascript中tostring()和valueof()的用法及两者的区别
Nov 16 Javascript
javascript定义类和类的实现实例详解
Dec 01 Javascript
解决jQuery上传插件Uploadify出现Http Error 302错误的方法
Dec 18 Javascript
jQuery实现简单滚动动画效果
Apr 07 Javascript
利用CSS、JavaScript及Ajax实现图片预加载的三大方法
Jan 22 Javascript
React实现全局组件的Toast轻提示效果
Sep 21 Javascript
vue项目查看vue版本及cli版本的实现方式
Oct 24 Javascript
Vue中插槽slot的使用方法与应用场景详析
Jun 08 Vue.js
基于angular实现树形二级表格
Oct 16 Javascript
js创建jsonArray传输至后台及后台全面解析
Apr 11 #Javascript
javascript HTML5 Canvas实现圆盘抽奖功能
Apr 11 #Javascript
详解JavaScript的另类写法
Apr 11 #Javascript
详解jQuery中的empty、remove和detach
Apr 11 #Javascript
JQuery导航菜单选择特效
Apr 11 #Javascript
JavaScript实现图片自动加载的瀑布流效果
Apr 11 #Javascript
javascript冒泡排序小结
Apr 10 #Javascript
You might like
Zend Studio去除编辑器的语法警告设置方法
2012/10/24 PHP
php类中private属性继承问题分析
2012/11/01 PHP
PHP图像处理类库及演示分享
2015/05/17 PHP
php好代码风格的阶段性总结
2016/06/25 PHP
thinkphp 抓取网站的内容并且保存到本地的实例详解
2017/08/25 PHP
PHP操作MySQL中BLOB字段的方法示例【存储文本与图片】
2017/09/15 PHP
用javascript实现自定义标签
2007/05/08 Javascript
JavaScript的21条基本知识点
2014/03/04 Javascript
Javascript中arguments和arguments.callee的区别浅析
2015/04/24 Javascript
jQuery实现购物车表单自动结算效果实例
2015/08/10 Javascript
基于jquery css3实现点击动画弹出表单源码特效
2015/08/31 Javascript
JS+CSS实现自适应选项卡宽度的圆角滑动门效果
2015/09/15 Javascript
详解AngularJS中自定义过滤器
2015/12/28 Javascript
JS利用cookies设置每隔24小时弹出框
2017/04/20 Javascript
vue如何将v-for中的表格导出来
2018/05/07 Javascript
ES6入门教程之Array.from()方法
2019/03/23 Javascript
纯javascript实现选择框的全选与反选功能
2019/04/08 Javascript
JS实现的碰撞检测与周期移动完整示例
2019/09/02 Javascript
js实现3D旋转相册
2020/08/02 Javascript
用js实现放大镜效果
2020/10/28 Javascript
python登录pop3邮件服务器接收邮件的方法
2015/04/30 Python
在python3.5中使用OpenCV的实例讲解
2018/04/02 Python
pygame游戏之旅 如何制作游戏障碍
2018/11/20 Python
python实现剪切功能
2019/01/23 Python
PyQt5实现简单数据标注工具
2019/03/18 Python
基于OpenCV python3实现证件照换背景的方法
2019/03/22 Python
对Python中class和instance以及self的用法详解
2019/06/26 Python
python集合的创建、添加及删除操作示例
2019/10/08 Python
numpy.array 操作使用简单总结
2019/11/08 Python
python通过文本在一个图中画多条线的实例
2020/02/21 Python
编译 pycaffe时报错:fatal error: numpy/arrayobject.h没有那个文件或目录
2020/11/29 Python
什么是反射?如何实现反射?
2016/07/25 面试题
一些网络技术方面的面试题
2014/05/01 面试题
女娲补天教学反思
2014/02/05 职场文书
决心书格式及范文
2019/06/24 职场文书
Django利用AJAX技术实现博文实时搜索
2021/05/06 Python