浏覽器對靜态檔案的緩存主要是通過cache-control來控制的,cache-control可以設定no-cache,max-age以及must-revalidate等來設定緩存政策。
如果max-age> 0則會在max-age的時間内不通路伺服器,用本地緩存的靜态檔案代替。
如果max-age<=0則會每次都詢問伺服器,資源檔案是否有修改,有則200,無則304。這相當于f5操作。
no-cache表示不理會緩存協商,全部重新加載。這相當于是ctrl+f5操作。
must-revalidate表示必須遵循政策規則。因為浏覽器有時候會提取過期的緩存,設定了這個政策後,浏覽器會嚴格遵循客戶設定的政策。
伺服器在首次應答客戶的request時,會傳回last-modified和etag給浏覽器,浏覽器将cache住這些資訊,下一次request伺服器時就會在header裡帶上if-modified-since和if-non-match資訊,這兩個分别對應last-modified和etag。伺服器會提取對應資源的modified時間和etag來做對比,如果有改變則傳回200并且response last-modified和etag給用戶端,沒變則傳回304不需要改變。
目前tomcat已經支援etag, tomcat是根據檔案的contentlength和lastmodified混合編碼生成串兒。因為last-modified因為隻支援到秒級,是以對于秒内頻繁修改的靜态資源效率會比較低下,etag則很好的避免了這一點。
一般對于靜态資源,伺服器端會通過過濾機制,自動為響應的header裡添加max-age資訊,這樣浏覽器就會在本地cache住這些資源。
我們最近的一個項目,前台使用extjs,使用extjs帶來的成本就是所有的頁面幾乎都是js頁面了。因為靜态js的檔案量大,且我們系統的營運地點非洲網絡條件不太好,帶寬資源比較寶貴,不能承受頻繁的靜态資源請求(這裡需要提一點,即使是web伺服器最終響應不需要更改的304請求,對系統也是一次帶寬開銷,我們也想盡力避免),于是我們想通過cache-contro将js檔案cache在本地。但是這就帶來一個問題,對于緊急釋出的一些前台界面,因為逾時機制會無法及時在系統層面展現。
是以我們必須實作一種機制,在釋出了新的js後,對應的引用該js的地方都要能自動重新整理。是以最簡單的方法就是每次編譯完後生成一個版本号,然後對每個引用js的url都添加上版本号就ok。這樣就可以保證在一次釋出周期内始終隻有一個js版本。
extjs實作了一套動态加載政策,可以通過js語言的方式去直接引入一個資源(ext.require),這和我們平時使用的靜态引用(<script src=" xxx")是完全不同的。是以決定研究一下extjs的動态加載機制。
目前網上有開源的extjs4的源碼,在動态load script時,會讀取config配置資訊,config可以通過loader.setconfig來配置。config裡有兩個參數需要注意,一個是disablecachingparam還有一個是disablecaching,後者代表是否需要為每一個請求都聲稱一個版本資訊,前者是版本資訊的參數名,預設是_dc.
loadscriptfile: function(url, onload, onerror, scope, synchronous) {
if (isfileloaded[url]) {
return loader;
}
var config = loader.getconfig(),
nocacheurl = url + (config.disablecaching ? ('?' + config.disablecachingparam + '=' + ext.date.now()) : ''),
iscrossoriginrestricted = false,
if (!synchronous) {
onscripterror = function() {
//<debug error>
onerror.call(scope, "failed loading '" + url + "', please verify that the file exists", synchronous);
//</debug>
};
scriptelements[url] = loader.injectscriptelement(nocacheurl, onload, onscripterror, scope);
} else {
/**
* 組裝xmlhttprequest對象,根據浏覽器支援情況初始化activexobject或者xmlhttprquest
*/
try {
xhr.open('get', nocacheurl, false);
xhr.send(null);
} catch (e) {
iscrossoriginrestricted = true;
}
* 僞代碼,跨域失敗,執行onerror回調函數
*/
else if ((status >= 200 && status < 300) || (status === 304)
//<if isnonbrowser>
|| isphantomjs
//</if>
) {
//調用成功, 記錄調試資訊
ext.globaleval(xhr.responsetext + debugsourceurl);
onload.call(scope);
else {
/**
* 僞代碼,執行onerror回調函數
*/
// prevent potential ie memory leak
xhr = null;
}
代碼很好了解,同步時直接加載,并且通過eval方法直接執行響應的js檔案(responsetext),異步時則通過injectscriptelement加載,同步還是異步可以通過loader裡的syncmodeenabled來設定,預設是false。
注意下這個方法裡的nocacheurl,根據disablecaching決定要不要為url加上版本資訊,緩存無效時會預設以目前時間為版本号。
injectscriptelement: function(url, onload, onerror, scope, charset) {
var script = document.createelement('script'),
dispatched = false,
* 設定onload和onerror的回調函數,主要是通過dispatched參數做幂等性校驗
* 為了相容ie,script需要支援onreadystatechange事件,這個事件會又多個狀态,是以需要做幂等校驗
script.type = 'text/javascript';
script.onerror = onerrorfn;
charset = charset || config.scriptcharset;
if (charset) {
script.charset = charset;
//相容ie
if ('addeventlistener' in script ) {
script.onload = onloadfn;
} else if ('readystate' in script) { // for <ie9 compatability
script.onreadystatechange = function() {
if ( this.readystate == 'loaded' || this.readystate == 'complete' ) {
onloadfn();
}
script.onload = onloadfn;
script.src = url;
(loader.documenthead || document.getelementsbytagname('head')[0]).appendchild(script);
return script;
該方法主要是建立了一個script的元素,url是前面方法生成的。
因為我們需要的是基于釋出版本資訊的緩存,是以隻需要将nocacheurl的版本資訊由目前時間戳改成編譯産生的版本資訊即可。
最後,注意,因為extjs6做了比較大的重構,url生成挪到了ext.data.proxy.server裡的buildurl裡:
url = ext.urlappend(url, ext.string.format("{0}={1}", me.getcachestring(), ext.date.now())),搜尋這一行即可。