jquery의 최신 API는 이미 사용하기 매우 쉽지만 실제 작업에서는 여전히 2차 캡슐화를 수행해야 합니다. 이점은 다음과 같습니다. 1. 2차 캡슐화 후의 API는 더 간결하고 개인 사용 습관에 더 부합합니다. 2. 난수나 기타 매개변수 추가와 같은 Ajax 작업에 대한 일부 통합 처리를 수행할 수 있습니다. 동시에 직장에서는 실시간 요구 사항이 높지 않은 일부 Ajax 요청 데이터가 있다는 사실도 발견하게 됩니다. 처음 요청한 데이터를 캐시하더라도 동일한 요청이 다시 발행되면 이전에 캐시된 데이터를 직접 가져오며, 데이터 반환은 관련 기능에 영향을 미치지 않습니다. 이러한 수동 캐시 제어를 통해 Ajax 요청이 줄어들어 웹 페이지 성능을 향상시키는 데에도 도움이 됩니다. 이 기사에서는 이 두 가지 문제에 대한 나만의 접근 방식을 소개합니다. 교류와 수정을 환영합니다.
코드 다운로드 클릭 (주의: ajax를 사용하기 때문에 파일 프로토콜에서는 실행할 수 없고 http에서 실행해야 합니다)
1. jquery를 캡슐화하는 Ajax
사실 이 부분의 관련 내용은 이전 블로그에서 소개한 바 있으나, 자세한 설명 없이 인용만 했을 뿐입니다. 또한, Ajax 캐시 프록시 컴포넌트 구현 역시 이번 2차 캡슐화 이후 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; });
사용법:
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); });
이 코드에서는 10초 동안 Ajax 요청을 캐시할 수 있는 프록시 객체를 생성했습니다. 타이머를 사용하여 프록시 객체의 get 메서드를 5번 호출하여 동일한 요청을 보냈습니다.
결과를 보면 전체 코드는 24초 동안 실행되었으며 에이전트가 요청을 보낸 시점은 각각 4초, 8초, 12초, 16초, 20초였다. 프록시의 캐시 유효 시간은 10초이고 4초는 요청이 처음 전송되는 시간이므로 8초와 12초의 프록시가 동일한 요청을 보낼 때 실제 Ajax 요청이 이때 전송됩니다. 캐시 시간은 4초와 8초만 지났으므로 캐시는 여전히 유효합니다. 이 두 시점에서는 실제 Ajax 요청이 전송되지 않았지만 16초 요청이 전송되었을 때 첫 번째 요청의 캐시 시간 이후 12초가 지났습니다. 캐시가 만료되었으므로 프록시가 또 다른 실제 Ajax 요청을 보냈고 캐시가 새로 고쳐졌습니다. 20초 요청은 여전히 최신 캐시 유효 시간 내에 있으므로 실제 Ajax 요청은 전송되지 않았습니다. 마지막으로, 프록시가 5개의 요청을 보냈지만 2개의 서비스만 요청한 것을 네트워크에서 볼 수 있습니다. 캐시 유효 시간이 연장되면 백그라운드 요청 수가 줄어들 수 있습니다. 이는 프런트 엔드 성능을 향상시키는 열쇠이기도 합니다. 캐싱 프록시.
이 데모를 통해 캐싱 프록시의 역할을 더욱 명확하게 이해할 수 있기를 바랍니다.
4. 기사 요약
이 기사의 첫 번째 부분에 요약된 구현은 내 작업에서 많이 사용되었습니다. 적어도 저는 문제가 발생하지 않았습니다. 그러나 결국 구성 요소 구현에서는 문제가 발생하지 않았을 수도 있습니다. 아직은 제약이 많습니다. 방금 2부의 구현을 작업에 적용했습니다. 우연히 캐싱의 필요성을 고려한 기능이 있어서 비교적 간단한 구현을 작성했지만 실제로는 이미 문제를 해결할 수 있습니다. 이것이 작업의 방식입니다. 어떤 것들은 미리 완벽하게 설계할 필요가 없습니다. 먼저 문제를 해결한 다음 새로운 문제가 발생하면 다시 재구성하는 것이 더 나은 작업 방법입니다. 다음 블로그에서는 캐시 프록시를 활용한 또 다른 컴포넌트 구현 아이디어를 소개할 예정인데, 이는 지방자치단체 캐스케이드와 유사한 기능을 가지고 있지만 최대한 HTML 구조와 CSS에서 분리될 수 있는 좀 더 다양한 컴포넌트를 작성하고 싶습니다. 가능하니 계속해서 관심을 가져주세요.
위 기사는 jquery의 ajax 및 ajax 캐싱 프록시 구성 요소를 다시 요약한 것입니다. AjaxCache에 대한 자세한 설명은 편집자가 공유한 모든 내용을 참조할 수 있기를 바라며, Script Home을 지원해 주시길 바랍니다.