對(duì)jquery的ajax進(jìn)行二次封裝以及ajax緩存代理組件:AjaxCache詳解
雖然jquery的較新的api已經(jīng)很好用了, 但是在實(shí)際工作還是有做二次封裝的必要,好處有:1,二次封裝后的API更加簡(jiǎn)潔,更符合個(gè)人的使用習(xí)慣;2,可以對(duì)ajax操作做一些統(tǒng)一處理,比如追加隨機(jī)數(shù)或其它參數(shù)。同時(shí)在工作中,我們還會(huì)發(fā)現(xiàn),有一些ajax請(qǐng)求的數(shù)據(jù),對(duì)實(shí)時(shí)性要求不高,即使我們把第一次請(qǐng)求到的這些數(shù)據(jù)緩存起來,然后當(dāng)相同請(qǐng)求再次發(fā)起時(shí)直接拿之前緩存的數(shù)據(jù)返回也不會(huì)對(duì)相關(guān)功能有影響,通過這種手工的緩存控制,減少了ajax請(qǐng)求,多多少少也能幫助我們提高網(wǎng)頁的性能。本文介紹我自己關(guān)于這兩方面問題的做法,歡迎交流和指正。
點(diǎn)擊 代碼下載 (注:因?yàn)橛玫搅薬jax,所以不能在file協(xié)議下運(yùn)行,必須運(yùn)行在http下)
1. 封裝jquery的ajax
其實(shí)這個(gè)部分的相關(guān)內(nèi)容在之前的一篇博客引入過,只不過那里面只是引用,沒有詳細(xì)說明,另外ajax緩存代理組件的實(shí)現(xiàn)也是基于這個(gè)二次封裝之后的ajax組件的,所以有必要在這里對(duì)它詳細(xì)說明一下,雖然實(shí)現(xiàn)并不復(fù)雜(詳見注釋說明):
define(function (require, exports, module) { var $ = require('jquery'); //根據(jù)關(guān)鍵的幾個(gè)參數(shù)統(tǒng)一創(chuàng)建ajax對(duì)象 function create(_url, _method, _data, _async, _dataType) { //添加隨機(jī)數(shù) if (_url.indexOf('?') > -1) { _url = _url + '&rnd=' + Math.random(); } else { _url = _url + '?rnd=' + Math.random(); } //為請(qǐng)求添加ajax標(biāo)識(shí),方便后臺(tái)區(qū)分ajax和非ajax請(qǐng)求 _url += '&_ajax=1'; //返回jquery創(chuàng)建的ajax對(duì)象,以便外部拿到這個(gè)對(duì)象以后可以通過 //.done .fail .always來添加回調(diào) //這么做是為了保留jquery ajax中好用的部分 return $.ajax({ url: _url, dataType: _dataType, async: _async, method: _method, data: _data }); } //ajax就是本組件全局唯一的實(shí)例,它的實(shí)例方法通過后面的循環(huán)代碼添加 //methods對(duì)象配置ajax各個(gè)實(shí)例方法的參數(shù): //name: 方法名稱 //method: http請(qǐng)求方法,get or post //async: 發(fā)送請(qǐng)求時(shí)是否異步 //dataType: 返回的數(shù)據(jù)類型,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' } ]; //由于二次封裝需要對(duì)外提供的每個(gè)實(shí)例方法創(chuàng)建ajax的邏輯是相同的 //所以通過這種方式統(tǒng)一定義各個(gè)實(shí)例方法 //關(guān)鍵代碼為下面代碼中的那個(gè)立即調(diào)用的函數(shù) //它返回了一個(gè)新的閉包函數(shù)作為實(shí)例方法 for (var i = 0, l = methods.length; i < l; i++) { ajax[methods[i].name] = (function (i) { return function () { /** * 每個(gè)實(shí)例方法接收三個(gè)參數(shù) * 第一個(gè)表示要請(qǐng)求的地址 * 第二個(gè)表示要提交到后臺(tái)的數(shù)據(jù),是一個(gè)object對(duì)象,如{param1: 'value1'} * 第三個(gè)表示后臺(tái)返回的數(shù)據(jù)類型,最最常用的就是html or json,絕大部分情況下這個(gè)參數(shù)不用傳,會(huì)使用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; });
1)統(tǒng)一提供隨機(jī)數(shù)參數(shù)和ajax請(qǐng)求標(biāo)識(shí);
2)對(duì)jquery的api進(jìn)行了包裝,對(duì)外提供的方法更加清晰明了。
使用方式:
define(function (require, exports, module) { var Ajax = require('mod/ajax'); //以GET方式請(qǐng)求html內(nèi)容 Ajax.html('html/demo', { param1: 'value1', param2: 'value2' }).done(function(response){ //請(qǐng)求成功的回調(diào) }).fail(function(){ //請(qǐng)求失敗的回調(diào) }).always(function(){ //請(qǐng)求完成的回調(diào) }); //以GET方式請(qǐng)求json數(shù)據(jù) Ajax.get('api/demo', { param1: 'value1', param2: 'value2' }).done(function(response){ //請(qǐng)求成功的回調(diào) }).fail(function(){ //請(qǐng)求失敗的回調(diào) }).always(function(){ //請(qǐng)求完成的回調(diào) }); //以POST方式請(qǐng)求json數(shù)據(jù) Ajax.post('api/demo', { param1: 'value1', param2: 'value2' }).done(function(response){ //請(qǐng)求成功的回調(diào) }).fail(function(){ //請(qǐng)求失敗的回調(diào) }).always(function(){ //請(qǐng)求完成的回調(diào) }); //以GET方式發(fā)送同步請(qǐng)求,獲取json數(shù)據(jù) Ajax.syncGet('api/demo', { param1: 'value1', param2: 'value2' }).done(function(response){ //請(qǐng)求成功的回調(diào) }).fail(function(){ //請(qǐng)求失敗的回調(diào) }).always(function(){ //請(qǐng)求完成的回調(diào) }); //以POST方式發(fā)送同步請(qǐng)求,獲取json數(shù)據(jù) Ajax.syncPost('api/demo', { param1: 'value1', param2: 'value2' }).done(function(response){ //請(qǐng)求成功的回調(diào) }).fail(function(){ //請(qǐng)求失敗的回調(diào) }).always(function(){ //請(qǐng)求完成的回調(diào) }); });
由于這個(gè)組件的每個(gè)實(shí)例方法返回的對(duì)象就是$.ajax創(chuàng)建的對(duì)象,所以我們完全可以照常使用.done .fail .always來添加回調(diào),就跟直接用$.ajax沒有任何區(qū)別。為什么API要設(shè)計(jì)成html, get, post, syncGet, syncPost這幾個(gè)方法,而且連dataType基本都是固定的?
那是因?yàn)樵陧?xiàng)目中,我們完全可以約定在異步請(qǐng)求的時(shí)候只能用html或json這兩種dataType,其它dataType不允許,現(xiàn)在的web項(xiàng)目這兩種方式已經(jīng)完全夠用了,至少我沒有碰到過非得使用別的dataType不可的情況;而且在實(shí)際工作當(dāng)中html, get, post, syncGet, syncPost這幾個(gè)方法幾乎能夠涵蓋我們需要的所有異步請(qǐng)求的場(chǎng)景,每當(dāng)我們要用ajax的時(shí)候,無非考慮的就是get還是post,同步還是異步,請(qǐng)求的是html還是json這三個(gè)問題,通過它們就能把每個(gè)問題都解決了。當(dāng)然jsonp,rest API這兩種情況就另說了,這個(gè)組件不是為它們服務(wù)的,這也是它的局限性,它還是傾向于在傳統(tǒng)的web項(xiàng)目中使用。
2. ajax緩存代理
要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ajax緩存代理組件,首先要清楚這個(gè)緩存代理的作用,在本文開篇說到過緩存代理的應(yīng)用場(chǎng)景:當(dāng)使用緩存代理第一個(gè)發(fā)起某個(gè)請(qǐng)求時(shí),在請(qǐng)求成功后將數(shù)據(jù)緩存下來,然后當(dāng)再次發(fā)起相同請(qǐng)求時(shí)直接返回之前緩存的數(shù)據(jù),緩存代理的作用是控制何時(shí)發(fā)送請(qǐng)求去后臺(tái)加載數(shù)據(jù),何時(shí)不發(fā)送請(qǐng)求直接從緩存中讀取之前加載的數(shù)據(jù)。為了實(shí)現(xiàn)一個(gè)簡(jiǎn)單的緩存代理,有三個(gè)問題要解決:
1)代理對(duì)象必須與被代理的對(duì)象有相同的API
拿前面的Ajax組件來說,它提供有html, get , post, syncGet, syncPost方法,那么它的代理對(duì)象也必須同時(shí)具有這些方法,而且調(diào)用方式,傳入?yún)?shù)都必須完全一致,只有這樣,當(dāng)我們?cè)谑褂么韺?duì)象的時(shí)候,就跟在使用原組件對(duì)象沒有區(qū)別。而且在緩存代理內(nèi)部,在某些條件下是需要調(diào)用原組件對(duì)象發(fā)送ajax請(qǐng)求的,如果接口不同,調(diào)用方式不同,參數(shù)不同,如何能保證內(nèi)部能夠正確調(diào)用原組件對(duì)象呢?這個(gè)條件還有一個(gè)好處,就是當(dāng)我們下次不想使用代理對(duì)象的時(shí)候,能夠以最小的代價(jià)將代理對(duì)象替換為原組件對(duì)象。
這一點(diǎn)其實(shí)是設(shè)計(jì)模式中代理模式的基本要求。
2)緩存數(shù)據(jù)存儲(chǔ)時(shí)的緩存索引問題
也就是說我們以什么樣的索引才能保證同一個(gè)請(qǐng)求的數(shù)據(jù)在緩存之后,下次查找時(shí)還能根據(jù)請(qǐng)求信息查找到呢?ajax緩存有別于其它緩存的地方在于它請(qǐng)求的地址可能包含可變的參數(shù)值,同一個(gè)地址如果后面的參數(shù)不同,那么對(duì)應(yīng)的請(qǐng)求結(jié)果也就不一定相同,所以簡(jiǎn)單起見,可以考慮把請(qǐng)求地址跟請(qǐng)求參數(shù)統(tǒng)一作為緩存索引,這樣就能對(duì)緩存進(jìn)行簡(jiǎn)單管理。同時(shí)考慮到其它可變性,還應(yīng)有其它的一些要求,詳見后面組件實(shí)現(xiàn)中的注釋說明。
3)緩存有效時(shí)間
雖然要實(shí)現(xiàn)的緩存代理很簡(jiǎn)單,但是這個(gè)問題一定是要考慮的,每個(gè)緩存代理實(shí)例,能夠緩存數(shù)據(jù)的有效時(shí)間不一定相同,有的可能只緩存幾分鐘,有的可能緩存幾十分鐘,當(dāng)緩存時(shí)間失效時(shí),緩存代理就得刪除原來的緩存,然后重新去加載數(shù)據(jù)才行。
綜合這些問題,基于第一部分的Ajax組件,最終實(shí)現(xiàn)的緩存代理組件AjaxCache的代碼如下(有注釋詳解):
define(function (require, exports, module) { var $ = require('jquery'); var Ajax = require('mod/ajax'); //緩存列表 var cache = {}; /** * 生成緩存索引: * 由于索引是根據(jù)url和data生成的(data是一個(gè)對(duì)象,存放Ajax要提交到后臺(tái)的數(shù)據(jù)) * 所以要想同一個(gè)url,同樣的data能夠有效地使用緩存, * 切勿在url和data中包含每次可變的參數(shù)值,如隨機(jī)數(shù)等 * 比如有一個(gè)請(qǐng)求: * url: aaa/bbb/cccc?r=0.312738 * data: {name: 'json'} * 其中url后面的r是一個(gè)隨機(jī)數(shù),每次外部發(fā)起這個(gè)請(qǐng)求時(shí),r的值都會(huì)變化 * 由于r每次都不同,最終會(huì)導(dǎo)致緩存索引不相同,結(jié)果緩存就無法命中 * 注:隨機(jī)數(shù)可放置在原始的Ajax組件內(nèi) * * 還有:如果是同一個(gè)接口,最好在同一個(gè)頁面內(nèi),統(tǒng)一url的路徑類型,要么都是相對(duì)路徑,要么都是絕對(duì)路徑 * 否則也會(huì)導(dǎo)致緩存無法有效管理 */ function generateCacheKey(url, data) { return url + $.param(data); } return function (opts) { opts = opts || {}; var cacheInterval = opts.cacheInterval || (1000 * 60 * 60);//緩存有效時(shí)間,默認(rèn)60分鐘 var proxy = {}; for (var i in Ajax) { if (Object.prototype.hasOwnProperty.call(Ajax, i)) { //在proxy對(duì)象上定義Ajax組件每一個(gè)實(shí)例方法的代理 //注意這個(gè)立即調(diào)用的函數(shù)表達(dá)式 //它返回了一個(gè)閉包函數(shù)就是最終的代理方法 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) { //如果請(qǐng)求時(shí)間跟緩存開始時(shí)間的間隔在緩存有效時(shí)間范圍內(nèi),就表示緩存是有效的 isCacheValid = true; } else { //否則就把緩存清掉 delete cache[cacheKey]; } } if (isCacheValid) { //模擬一個(gè)異步任務(wù)來返回已經(jīng)緩存的數(shù)據(jù) //通過$defer延遲對(duì)象,可以保證這個(gè)模擬任務(wù)返回的對(duì)象跟原始Ajax組件調(diào)用返回的對(duì)象有相同的API //這是代理的關(guān)鍵:代理對(duì)象與被代理的對(duì)象應(yīng)該具有相同API //只有這樣當(dāng)我們?nèi)∠淼臅r(shí)候,不會(huì)對(duì)那些用了代理的組件進(jìn)行修改 var $defer = $.Deferred(); setTimeout(function () { $defer.resolve(cacheItem.res); }, 10); return $.when($defer); } //緩存失效或者沒有緩存的時(shí)候調(diào)用原始的Ajax組件的同名方法去后臺(tái)請(qǐng)求數(shù)據(jù) return Ajax[i].apply(Ajax, arguments).done(function (res) { //在請(qǐng)求成功之后將結(jié)果緩存,并記錄當(dāng)前時(shí)間作為緩存的開始時(shí)間 cache[cacheKey] = { res: res, cacheStartTime: +new Date() } }); } })(i); } } return proxy; }; });
3. 演示效果
為了說明緩存代理的使用效果,我做了一個(gè)演示效果:
其中的ajax.js就是第一部分的實(shí)現(xiàn),ajaxCache.js就是第二部分的實(shí)現(xiàn),演示頁面對(duì)應(yīng)代碼中的html/demo.html,相關(guān)js是js/app/demo.js:
define(function (require, exports, module) { var AjaxCache = require('mod/ajaxCache'); //創(chuàng)建代理對(duì)象 var Ajax = new AjaxCache({ cacheInterval: 10 * 1000 }); var count = 5; console.log('時(shí)間點(diǎn):第' + 0 + 's,定時(shí)器開始!'); var t = setInterval(function(){ if(count == 0) { console.log('時(shí)間點(diǎn):第' + (5 - count + 1) * 4 + 's,定時(shí)器結(jié)束!'); return clearInterval(t); } else{ console.log('時(shí)間點(diǎn):第' + (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); });
在這個(gè)代碼中,我創(chuàng)建了一個(gè)代理對(duì)象,能將ajax請(qǐng)求緩存10s,用一個(gè)定時(shí)器一共調(diào)用代理對(duì)象五次get方法來發(fā)送同一個(gè)請(qǐng)求,最終打印效果如下:
從結(jié)果來看,整個(gè)代碼執(zhí)行了24s,代理發(fā)送請(qǐng)求的時(shí)間點(diǎn)分別是第4s,第8s,第12s,第16s和第20s。由于代理的緩存有效時(shí)間是10s,且第4s是第一次發(fā)送請(qǐng)求,所以此時(shí)肯定會(huì)發(fā)送真實(shí)的ajax請(qǐng)求;當(dāng)?shù)?s和第12s的代理發(fā)送同一請(qǐng)求時(shí),由于距離緩存的時(shí)間只過去了4s和8s,所以緩存還是有效的,這兩個(gè)時(shí)間點(diǎn)都沒有發(fā)送實(shí)際的ajax請(qǐng)求;但是當(dāng)?shù)?6s的請(qǐng)求發(fā)送時(shí),距離第一次請(qǐng)求的緩存時(shí)間已經(jīng)過去12s,緩存已經(jīng)失效,所以代理又發(fā)送了一次真實(shí)的ajax請(qǐng)求,然后緩存被刷新;第20s的請(qǐng)求還是在最新的緩存有效時(shí)間內(nèi),所以也沒有發(fā)送實(shí)際的ajax請(qǐng)求。最后在network中可以看到代理發(fā)送了5次請(qǐng)求,但是只請(qǐng)求了2次服務(wù),如果把緩存有效時(shí)間延長,就能再減少請(qǐng)求后臺(tái)的次數(shù),這也是緩存代理對(duì)前端性能提升的關(guān)鍵。
希望這個(gè)演示效果能夠讓你更加清楚地了解緩存代理的作用。
4. 本文總結(jié)
本文第1部分總結(jié)的實(shí)現(xiàn)在我自己的工作中應(yīng)用很多,至少?zèng)]碰到什么問題,不過也有可能是我沒遇到,畢竟那個(gè)組件實(shí)現(xiàn)還是有不少約束的。第2部分的實(shí)現(xiàn)我也是剛剛應(yīng)用到工作中去,正好有一個(gè)功能我考慮到有緩存的必要性,于是就寫了一個(gè)較為簡(jiǎn)單的實(shí)現(xiàn),雖然簡(jiǎn)單,但是已經(jīng)能解決我的問題了,實(shí)際工作本來就是這樣,有些東西沒必要事無巨細(xì)的在事前就設(shè)計(jì)地很完美,先解決問題,然后在遇到新問題的時(shí)候再回來重構(gòu),有時(shí)也是一種更好的工作方法。下一篇博客介紹另外一個(gè)用到緩存代理的組件的實(shí)現(xiàn)思路,跟省市級(jí)聯(lián)類似的功能,不過我想的是寫成通用性更強(qiáng)的,能夠與html結(jié)構(gòu)和css盡可能分離的組件,請(qǐng)您繼續(xù)關(guān)注。
以上這篇對(duì)jquery的ajax進(jìn)行二次封裝以及ajax緩存代理組件:AjaxCache詳解就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解jQuery移動(dòng)頁面開發(fā)中的ui-grid網(wǎng)格布局使用
這篇文章主要介紹了jQuery移動(dòng)頁面開發(fā)中的ui-grid網(wǎng)格布局使用,以講解多列頁面布局方式為主,需要的朋友可以參考下2015-12-12JQuery select控件的相關(guān)操作實(shí)現(xiàn)代碼
JQuery獲取和設(shè)置Select選項(xiàng)方法匯總?cè)缦?,需要的朋友可以參考?/div> 2012-09-09JQuery記住用戶名密碼實(shí)現(xiàn)下次自動(dòng)登錄功能
這篇文章主要介紹了JQuery記住用戶名密碼實(shí)現(xiàn)下次自動(dòng)登錄功能,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-04-04js前臺(tái)判斷開始時(shí)間是否小于結(jié)束時(shí)間
js前臺(tái)判斷開始時(shí)間是否小于結(jié)束時(shí)間,結(jié)合了jquery需要的朋友可以參考下2012-02-02autoPlay 基于jquery的圖片自動(dòng)播放效果
效果類似這種,自動(dòng)播放,實(shí)質(zhì)控制層的顯示隱藏。需要的朋友可以參考下。2011-12-12兩個(gè)多選select(multiple左右)添加、刪除選項(xiàng)和取值實(shí)例
這篇文章主要介紹了兩個(gè)多選select(multiple左右)添加、刪除選項(xiàng)和取值實(shí)例,使用jquery實(shí)現(xiàn),需要的朋友可以參考下2014-05-05最新評(píng)論