对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筛选器全系列介绍
Aug 27 Javascript
js中的replace方法使用介绍
Oct 28 Javascript
jQuery中:animated选择器用法实例
Dec 29 Javascript
jQuery使用empty()方法删除元素及其所有子元素的方法
Mar 26 Javascript
jquery判断类型是不是number类型的实例代码
Oct 07 Javascript
javascript使用btoa和atob来进行Base64转码和解码
Mar 20 Javascript
使用mint-ui开发项目的一些心得(分享)
Sep 07 Javascript
jQuery中复合选择器简单用法示例
Mar 31 jQuery
vue-router重定向不刷新问题的解决
Jun 25 Javascript
详解Vue.directive 自定义指令
Mar 27 Javascript
多个vue子路由文件自动化合并的方法
Sep 03 Javascript
浅谈Web Storage API的使用
Jun 23 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
页面只有一个text的时候,回车自动submit的解决方法
2010/08/12 Javascript
图片img的src不变让浏览器重新加载实现方法
2013/03/29 Javascript
javascript动态添加样式(行内式/嵌入式/外链式等规则)
2013/06/24 Javascript
JS 屏蔽按键效果与改变按键效果的示例代码
2013/12/24 Javascript
javascript 寻找错误方法整理
2014/06/15 Javascript
jQuery实现网页顶部固定导航效果代码
2015/12/24 Javascript
可输入文字查找ajax下拉框控件 ComBox的实现方法
2016/10/25 Javascript
WEB 前端开发中防治重复提交的实现方法
2016/10/26 Javascript
js 性能优化之快速响应的用户界面
2017/02/15 Javascript
浅谈React中的元素、组件、实例和节点
2018/02/27 Javascript
node实现基于token的身份验证
2018/04/09 Javascript
JS双向链表实现与使用方法示例(增加一个previous属性实现)
2019/01/31 Javascript
前端Vue项目详解--初始化及导航栏
2019/06/24 Javascript
js+canvas实现纸牌游戏
2020/03/16 Javascript
Python Web框架Flask中使用百度云存储BCS实例
2015/02/08 Python
Python中几个比较常见的名词解释
2015/07/04 Python
详解Python 数据库 (sqlite3)应用
2016/12/07 Python
使用python调用zxing库生成二维码图片详解
2017/01/10 Python
python检测文件夹变化,并拷贝有更新的文件到对应目录的方法
2018/10/17 Python
python保存二维数组到txt文件中的方法
2018/11/15 Python
在python下使用tensorflow判断是否存在文件夹的实例
2019/06/10 Python
Python学习笔记之迭代器和生成器用法实例详解
2019/08/08 Python
详解在Python中以绝对路径或者相对路径导入文件的方法
2019/08/30 Python
python安装virtualenv虚拟环境步骤图文详解
2019/09/18 Python
Python写出新冠状病毒确诊人数地图的方法
2020/02/12 Python
纯CSS实现菜单、导航栏的3D翻转动画效果
2014/04/23 HTML / CSS
HTML5 通过Vedio标签实现视频循环播放的示例代码
2020/08/05 HTML / CSS
Lancer Skincare官方网站:抗衰老皮肤护理
2020/11/20 全球购物
高中微机老师自我鉴定
2014/02/16 职场文书
人事经理岗位职责范本
2014/08/04 职场文书
2014年小学工作总结
2014/11/26 职场文书
浅谈:电影《孔子》观后感(范文)
2019/10/14 职场文书
导游词之日本富士山
2020/01/06 职场文书
在Windows下安装配置CPU版的PyTorch的方法
2021/04/02 Python
iPhone13 Pro外观确定,升级4800万镜头,4月20日发新品
2021/04/15 数码科技
Apache Pulsar集群搭建部署详细过程
2022/02/12 Servers