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

