Although the newer API of jquery is already very easy to use, it is still necessary to do secondary encapsulation in actual work. The benefits are: 1. The API after secondary encapsulation is more concise and more in line with personal usage habits; 2. You can perform some unified processing on ajax operations, such as appending random numbers or other parameters. At the same time at work, we will also find that there are some ajax requested data that do not have high real-time requirements. Even if we cache the data requested for the first time, then when the same request is issued again, we will directly get the previously cached data. Data return will not have any impact on related functions. Through this manual cache control, ajax requests are reduced, which can also help us improve the performance of the web page. This article introduces my own approach to these two issues. I welcome exchanges and corrections.
Click to download the code (Note: Because ajax is used, it cannot run under the file protocol and must run under http)
1. Ajax encapsulating jquery
In fact, the relevant content of this part was introduced in a previous blog, but it was only quoted without detailed explanation. In addition, the implementation of the ajax cache proxy component is also based on the ajax component after this secondary encapsulation, so there is It is necessary to explain it in detail here, although the implementation is not complicated (see the comments for details):
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; });
Usage:
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. 演示效果
为了说明缓存代理的使用效果,我做了一个演示效果:
其中的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); });
In this code, I created a proxy object that can cache ajax requests for 10 seconds. I used a timer to call the get method of the proxy object five times to send the same request. The final printing effect is as follows:
Judging from the results, the entire code was executed for 24 seconds, and the time points when the agent sent the request were the 4th, 8th, 12th, 16th and 20th seconds respectively. Since the cache validity time of the proxy is 10s, and the 4s is the first time a request is sent, a real ajax request will definitely be sent at this time; when the proxy at the 8s and 12s sends the same request, since the cache time has only passed 4s and 8s, so the cache is still valid. No actual ajax request was sent at these two points in time; but when the 16s request was sent, 12s had passed since the cache time of the first request, and the cache had expired, so The proxy sent another real ajax request, and then the cache was refreshed; the 20s request was still within the latest cache validity time, so no actual ajax request was sent. Finally, you can see in the network that the proxy sent 5 requests, but only requested 2 services. If the cache validity time is extended, the number of background requests can be reduced. This is also the key to improving front-end performance of the caching proxy.
I hope this demonstration can give you a clearer understanding of the role of the caching proxy.
4. Summary of this article
The implementation summarized in the first part of this article has been used a lot in my own work. At least I have not encountered any problems. However, it may be that I have not encountered them. After all, the component implementation still has many constraints. I have just applied the implementation of part 2 to my work. There happened to be a function that I considered the necessity of caching, so I wrote a relatively simple implementation. Although it is simple, it can already solve my problem. In fact, This is how work is. Some things don't need to be designed perfectly in advance. Solving the problem first, and then coming back to reconstruct it when new problems are encountered is sometimes a better way of working. The next blog will introduce the implementation idea of another component using cache proxy, which has similar functions to provincial and municipal cascades. However, I want to write a more versatile component that can be separated from the HTML structure and CSS as much as possible. Please continue to pay attention.
The above article re-encapsulates jquery's ajax and ajax caching proxy components: AjaxCache detailed explanation is all the content shared by the editor. I hope it can give you a reference, and I hope you will support Script Home.