客戶端緩存腳本通常讓我們又愛又恨,愛他,是因為他確實可以有效防止相同的文件在客戶端和服務器之間傳來傳去,恨他,是因為當你真的需要更新他的時候,他可能不理會你的要求。
以至於很多人直接在腳本后面加一個時間戳作為參數,當我們每次去獲取網頁的時候,都會在后面去增加一個時間戳,這樣腳本文件就會每次都回傳給瀏覽器,具體表現為你每次F5刷新頁面(不是CTRL+F5)的時候,返回狀態碼始終都是200。
當然,這么殘暴地寫,在一些企業內網的環境,也沒什么大不了,但是還是很多人會有點兒潔癖。其實只要在每次變更的時候強制大家刷新一下,不是就可以了嗎?於是大家認為在后面加一個版本號,如v=2之類的,這樣只需要每次更新腳本后,在引用的頁面,更新這個v,就可以讓客戶端更新腳本了。
在這里,推薦大家用IE的F12開發者工具來抓網絡包,當然Fiddler等也都可以,不過確實在這件事上,一個是沒必要,一個可能Fiddler在修改IE端口指向8000之后可能會有問題。
回到話題,這里需要解釋一下瀏覽器對客戶端緩存看的是整條URL包括后面的參數,所以當你有個地方引用:
http://volnet.cnblogs.com/scripts/demo.js 的時候,你再次訪問
http://volnet.cnblogs.com/scripts/demo.js?v=2 的時候,該緩存仍然在你的瀏覽器里呆着,當你下次繼續訪問不帶參數的demo.js的時候,你引用的仍然還是舊的文件。
你可能對此表示不以為然,因為大部分的腳本通常都是你自己在引用,因此你更新他們的時候,總是很容易。但是有的時候,你的腳本會被第三方引用,當你變更腳本的時候,你希望他們盡量少去改動,這時候,你可能就會遇到客戶端無法刷新腳本的問題。
這時候你可能會想到Last-Modified和ETag標簽,這些標簽的出現,既可以替代在js文件后面加版本號或者時間戳的問題,在服務器修改他們后,通常可以被監測到,並修改服務器的ETag值,當下次從客戶瀏覽器傳回If-None-Match和If-Modified-Since的時候就可以在服務器判斷是應該返回304呢,還是返回200呢?看上去挺美好的事情,經常會有各種各樣的意想不到。誰知道這些看上去很簡單的東西,並不是每個瀏覽器都具備的能力,而且可能的原因還來自各種各樣千奇百怪的客戶端設置,拋開他們的問題而言。擺在眼前的事實就是,客戶端的腳本就是因為304而不更新了,你能怎么辦?你抓回來的包,可以證明你的服務器下發了ETag,但人家就是不給你返回If-None-Match,你也不太可能在服務器去修改腳本的版本號,難道你要讓大家都按Ctrl+F5么?(可憐的事情還真不是發生在一些過時的瀏覽器上,今天找到的幾個問題,IE9/IE8全都遇上了)。
這里有個問題需要說一下,就是當你的文件已經是304的時候,除非服務器支持ETag並且你的客戶端帶回If-None-Match標記,或者是Last-Modified和If-Modifed-Since組合的時候,原來那個鏈接,基本上都不會給你返回200,這或許是你早期的服務器設置所造成的,他不會因為你重新下發ETag,而讓他們去給你返回這些值,除非你這次是200,並同時下發了那些用來緩存的標記。這個結論是我推導出來的,可能是IE9的行為bug,或者時設計使然。
我們如何避免他們呢?
我想來想去,既然服務器在我手里,我可以控制,並且它確實下發了ETag,那么我何不就借服務器的能力,讓它把狀態200發下去呢?激發它下發狀態200,就是讓它回傳一個不一樣的ETag值(在客戶端叫If-None-Match),這樣服務器自然就會下發了,而那些一直緩存不更新的客戶,通常是因為沒有帶任何與之相關的參數,而宣布刷新資源失敗。這里我用了XMLHttpRequest去發一個GET請求,並把驅動狀態碼200的必要條件給帶上,就可以了。
var httpCacheUtil = { createXHR: function () { if (typeof XMLHttpRequest != "undefined") { return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined") { if (typeof arguments.callee.activeXString != "string") { var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"]; for (var i = 0, len = versions.length; i < len; ++i) { try { var xhr = new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; return xhr; } catch (ex) { // pass } } } return new ActiveXObject(arguments.callee.activeXString); } else { throw new Error("No XHR object available"); } }, update: function(url){ try { var success = function(responseText) { }; var error = function(errorStatus) { }; var xhr = httpCacheUtil.createXHR(); if(typeof xhr != "undefined" && xhr != null) { xhr.onreadystatechange = function (event) { if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { if (typeof success === "function") success(xhr.responseText); } else { if (typeof error === "function") error(xhr.status); } } }; xhr.open("GET", url, false); xhr.setRequestHeader("If-None-Match","\"22426f327b8cd1:0\""); xhr.setRequestHeader("If-Modified-Since", "Sat, 31 Dec 2011 02:51:00 GMT"); xhr.send(null); } } catch(e){ // throw no exception } } }; httpCacheUtil.update("http://volnet.cnblogs.com/Scripts/demo1.js"); httpCacheUtil.update("http://volnet.cnblogs.com/Scripts/demo2.js");
那些關於ETag和Last-Modifed,Cache-Control:no-cache等的說明文檔,在網上已經很多了,大家可以參考相關資料來了解瀏覽器緩存的相關知識。