前言
網站設計的優化是一個很大的話題,有一些通用的原則,也有針對不同開發平台的一些建議。這方面的研究一直沒有停止過,我在不同的場合也分享過這樣的話題。
作為通用的原則,雅虎的工程師團隊曾經給出過35個最佳實踐。這個列表請參考 Best Practices for Speeding Up Your Web Site http://developer.yahoo.com/performance/rules.html,同時,他們還發布了一個相應的測試工具Yslow http://developer.yahoo.com/yslow/
我強烈推薦所有的網站開發人員都應該學習這些最佳實踐,並結合自己的實際項目情況進行應用。 接下來的一段時間,我將結合ASP.NET這個開發平台,針對這些原則,通過一個系列文章的形式,做些講解和演繹,以幫助大家更好地理解這些原則,並且更好地使用他們。
准備工作
為了跟隨我進行后續的學習,你需要准備如下的開發環境和工具
- Google Chrome 或者firefox ,並且安裝 Yslow這個擴展組件.請注意,這個組件是雅虎提供的,但目前沒有針對IE的版本。
- https://chrome.google.com/webstore/detail/yslow/ninejjcohidippngpapiilnmkgllmakh
- https://addons.mozilla.org/en-US/firefox/addon/yslow/
- 你應該對這些瀏覽器的開發人員工具有所了解,你可以通過按下F12鍵調出這個工具。
- Visaul Studio 2010 SP1 或更高版本,推薦使用Visual Studio 2012
- 你需要對ASP.NET的開發基本流程和核心技術有相當的了解,本系列文章很難對基礎知識做普及。
本文要討論的話題
這一篇我和大家討論的是第十四條原則:Make Ajax Cacheable (使AJAX調用盡可能利用緩存特性)。
AJAX的基本概念
- AJAX = Asynchronous JavaScript and XML(異步的 JavaScript 和 XML)。
- AJAX 不是新的編程語言,而是一種使用現有標准的新方法。
- AJAX 是與服務器交換數據並更新部分網頁的藝術,在不重新加載整個頁面的情況下。
AJAX的典型應用場景
AJAX在目前的應用程序中使用非常廣泛,為網站提供了更加豐富的效果(雖然技術很早就有,但最早引起大家注意是在2004年左右的Gmail中)。其典型的應用場景包括
- 異步加載,使得頁面的內容可以分批加載。
- 局部更新,使得頁面的局部更新不會導致頁面的刷新。
由於AJAX其實也是需要發起請求,然后服務器執行,並將結果(通常是JSON格式的)發送給瀏覽器進行最后的呈現或者處理,所以對於網站設計優化的角度而言,我們同樣需要考慮對這些請求,是否可以盡可能地利用到緩存的功能來提高性能。
【備注】關於AJAX,以及它與目前的一些技術(主要是服務器端的技術)如何結合的文檔,我之前寫過很多,有興趣的朋友可以先參考一下 http://www.google.ee/search?q=site%3Awww.cnblogs.com%2Fchenxizhang%2F%20ajax
什么樣的AJAX請求可以被緩存?
對服務器請求進行優化的方法有很多,我之前已經寫過幾篇,這些原則也可以應用在AJAX的場景中
- 優化網站設計(三):對資源添加緩存控制
- 優化網站設計(四):對資源啟用壓縮
- 優化網站設計(九):減少DNS查找的次數
- 優化網站設計(十):最小化JAVASCRIPT和CSS
- 優化網站設計(十一):避免重定向
- 優化網站設計(十三):配置ETags
但是,對於AJAX而言,有一些特殊性,並不是所有的AJAX請求都是可以緩存的。這是由於AJAX的請求通常有兩種不同的方法:POST和GET。他們在進行請求的時候,就會略有不同。
- POST的請求,是不可以在客戶端緩存的,每次請求都需要發送給服務器進行處理,每次都會返回狀態碼200。(這里可以優化的是,服務器端對數據進行緩存,以便提高處理速度)
- GET的請求,是可以(而且默認)在客戶端進行緩存的,除非指定了不同的地址,否則同一個地址的AJAX請求,不會重復在服務器執行,而是返回304。
針對POST的情況如何優化
POST的請求,瀏覽器通常會假定用戶是想要提交(或者發送)數據給服務器,既然如此,那么瀏覽器自然就不會對該請求進行緩存,因為你是提交數據,所以它認為服務器自然每次都是需要處理的。我們可以來看一個例子。
using System; using System.Web.Services; namespace WebApplication4 { /// <summary> /// Summary description for HelloWebService /// </summary> [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. [System.Web.Script.Services.ScriptService] public class HelloWebService : System.Web.Services.WebService { [WebMethod] public string HelloWorld() { return string.Format("Hello,world -- {0}", DateTime.Now); } } }
上面是一個簡單的XML Web Service的定義。需要注意的是,如果希望支持AJAX訪問的話,必須要添加ScriptService這個Attribute。
我們的調用代碼如下:
//XML Web Service只支持POST,這種方式無法在瀏覽器中緩存,但可以結合服務器端的緩存,減少后台代碼執行的次數來提高性能 $("#btCallXMLWebService").click(function () { $.ajax({ type: "POST", contentType: "application/json;utf-8", url: "HelloWebService.asmx/HelloWorld", data: null, dataType: "json", success: function (result) { alert(result.d); } }); });
運行起來之后,我們多次點擊這個按鈕,會截獲到如下的請求:
根據上面的截圖不難看出,其實每次都請求都是重新被處理過的,它們都是返回狀態碼為200。
這就是POST AJAX請求的處理情況,它不會被客戶端緩存。那你可能會說,能不能將type改為GET呢?例如下面這樣
$("#btCallXMLWebService").click(function () { $.ajax({ type: "GET", contentType: "application/json;utf-8", url: "HelloWebService.asmx/HelloWorld", data: null, dataType: "json", success: function (result) { alert(result.d); } }); });
答案是,針對XML Web Service或者標准的WCF服務,它們不支持通過GET進行請求,只支持POST請求。
服務器返回了狀態碼為500的錯誤,並且在正文里面描述了這個錯誤的信息,如下圖所示
那么,針對這種場景,我們是否有什么方法進行優化呢?
using System; using System.Web.Services; namespace WebApplication4 { /// <summary> /// Summary description for HelloWebService /// </summary> [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. [System.Web.Script.Services.ScriptService] public class HelloWebService : System.Web.Services.WebService { /// <summary> /// 該方法被緩存了10秒鍾,是將結果緩存在服務器內存中。 /// </summary> /// <returns></returns> [WebMethod(CacheDuration=10)] public string HelloWorld() { return string.Format("Hello,world -- {0}", DateTime.Now); } } }
這樣修改之后,對於客戶端而言,其實沒有什么改變的,多次調用的時候,服務器都需要處理,然后返回狀態碼為200。但是區別是什么呢?區別在於服務器並不需要每次都運行真正的代碼,它將結果緩存在內存中,在10秒之內重復調用,就直接返回該內存中的數據即可。(這樣可以提高服務器的性能,提高並發性)
【備注】如果是WCF來做服務的話,默認是不支持直接對操作進行緩存的。
如何設計支持GET的服務
我們了解到默認情況下,XML Web Service和WCF服務,都只支持使用POST方法的AJAX調用。那么是否有辦法設計出來一個支持GET的服務呢?
- XML Web Service不支持GET
- WCF服務,可以通過一個特殊的webHttpBinding支持GET。本文將討論這一種做法。
【備注】WCF有多種適用場景,我之前寫過兩篇文章,有興趣的朋友可以參考
3. ASP.NET MVC中可以支持Web API這個功能,可以通過GET的方式進行調用。這個的做法,本文不做探討,有興趣的朋友可以參考 http://www.asp.net/web-api
我們來看一個例子。在WCF中支持一種特殊的Operation,就是WebGet
using System; using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Web; namespace WebApplication4 { [ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class HelloWCFService { [WebGet(ResponseFormat = WebMessageFormat.Json)] public string RestfulHelloWorldWithParameter(string name) { return string.Format("Hello,{0} -- {1}", DateTime.Now, name); } } }
AJAX調用代碼如下
$("#btCallRestfulWCFServicebwithParameter").click(function () { var name = $("#txtName").val(); //GET請求默認就是會被緩存(在同一個瀏覽器中,默認是臨時緩存,瀏覽器一關閉就刪除掉) $.getJSON("HelloWCFService.svc/RestfulHelloWorldWithParameter", { name: name }, function (data) { alert(data.d); }); });
運行起來之后,我們分別輸入不同的name參數,並且分別調用兩次。
我們可以發現,第一次調用會返回狀態碼(200),而第二次調用會返回狀態碼(304),但如果參數不一樣,又會返回狀態碼(200)。依次類推。
我們也確實可以在瀏覽器緩存中找到兩份緩存的數據
所以對於GET請求,默認就會被緩存。但是,如果你想改變這個行為,例如你有時候不想做緩存,應該如何來實現呢?
避免對GET請求做緩存
有的時候,我們可能希望GET請求不被緩存,有幾種做法來達到這樣的目的。
- 每次調用的時候,請求不同的地址(可以在原始地址后面添加一個隨機的號碼)例如下面這樣:
$("#btCallRestfulWCFServicebwithParameter").click(function () { var name = $("#txtName").val(); //GET請求默認就是會被緩存(在同一個瀏覽器中,默認是臨時緩存,瀏覽器一關閉就刪除掉) $.getJSON("HelloWCFService.svc/RestfulHelloWorldWithParameter", { name: name,version:Math.random() }, function (data) { alert(data.d); }); });
- 如果你所使用的是jquery的話,則可以考慮禁用AJAX的緩存
$.ajaxSetup({ cache: false });
使用HTML5的新特性來減少不必要的AJAX調用
我覺得一個比較徹底的做法是,考慮將一部分數據緩存在客戶端中,而且最好不要在臨時文件夾中,以便下次啟動時還能使用到這些數據。HTML 5中提供了一個新的特性:local storage,可以很好地解決這個問題,如果有興趣的朋友可以參考下面的文檔