jquery の新しい API はすでに非常に使いやすいですが、実際の作業では依然として二次カプセル化を行う必要があります。利点は次のとおりです。 1. 二次カプセル化後の API はより簡潔で、個人の使用習慣に沿ったものになります。 ; 2. 乱数やその他のパラメーターの追加など、ajax 操作でいくつかの統合処理を実行できます。同時に、作業中に、最初にリクエストされたデータをキャッシュしたとしても、同じリクエストが再度発行されると、リアルタイム性の要件が高くない ajax リクエスト データがいくつかあることもわかります。以前にキャッシュされたデータを直接取得します。データの戻りは関連する機能に影響を与えません。この手動キャッシュ制御により、Ajax リクエストが削減され、Web ページのパフォーマンスの向上にも役立ちます。この記事では、これら 2 つの問題に対する私なりのアプローチを紹介します。意見交換や修正は歓迎です。
クリックしてコードをダウンロードします (注: ajax が使用されているため、ファイル プロトコルでは実行できず、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; });
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); });
このコードでは、Ajax リクエストを 10 秒間キャッシュできるプロキシ オブジェクトを作成しました。タイマーを使用してプロキシ オブジェクトの get メソッドを 5 回呼び出して、同じリクエストを送信しました。最終的な印刷効果は次のとおりです。
結果から判断すると、コード全体は 24 秒間実行され、エージェントがリクエストを送信した時点はそれぞれ 4 秒、8 秒、12 秒、16 秒、20 秒でした。プロキシのキャッシュ有効期間は 10 秒で、リクエストが初めて送信されるのは 4 秒であるため、8 秒と 12 秒のプロキシが同じリクエストを送信すると、この時点で実際の Ajax リクエストが送信されます。キャッシュ時間は 4 秒と 8 秒しか経過していないため、これらの 2 つの時点では実際の ajax リクエストは送信されていませんが、16 秒のリクエストが送信された時点では、最初のリクエストのキャッシュ時間から 12 秒が経過していました。キャッシュの有効期限が切れていたため、プロキシは別の実際の ajax リクエストを送信しましたが、その後キャッシュが更新されました。そのリクエストはまだ最新のキャッシュ有効期間内だったので、実際の ajax リクエストは送信されませんでした。最後に、ネットワークでは、プロキシが 5 つのリクエストを送信しましたが、リクエストしたサービスは 2 つだけであることがわかります。キャッシュの有効期間が延長されると、これがフロントエンドのパフォーマンスを向上させる鍵となります。キャッシングプロキシ。
このデモンストレーションで、キャッシング プロキシの役割をより明確に理解していただければ幸いです。
4. この記事の概要
この記事の最初の部分でまとめた実装は、私自身の仕事でよく使用されていますが、結局のところ、問題に遭遇したことがないのかもしれません。まだ多くの制約があります。パート 2 の実装を自分の仕事に適用したところです。たまたまキャッシュが必要だと考えた関数があったので、簡単ではありますが、実際にはすでに問題を解決できます。仕事というのは、事前に完璧に設計する必要がないものもあります。最初に問題を解決し、新しい問題が発生したときに戻ってそれを再構築する方が良い場合もあります。次回のブログでは、地方自治体のカスケードと同様の機能を持つキャッシュプロキシを利用した別コンポーネントの実装アイデアを紹介しますが、できるだけHTML構造やCSSから分離できる、より汎用性の高いコンポーネントを書きたいと思っています。可能性がありますので、引き続きご注意ください。
上記の記事は、jquery の ajax と ajax キャッシュ プロキシ コンポーネントを再カプセル化しています。AjaxCache の詳細な説明は、エディターによって共有されたすべての内容です。参考にしていただければ幸いです。また、Script Home をサポートしていただければ幸いです。