前言
網站設計的優化是一個很大的話題,有一些通用的原則,也有針對不同開發平台的一些建議。這方面的研究一直沒有停止過,我在不同的場合也分享過這樣的話題。
作為通用的原則,雅虎的工程師團隊曾經給出過35個最佳實踐。這個列表請參考 Best Practices for Speeding Up Your Web Sitehttp://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的開發基本流程和核心技術有相當的了解,本系列文章很難對基礎知識做普及。
本文要討論的話題
這一篇我和大家討論的是第十六條原則:Reduce the Number of DOM Elements (減少DOM元素的數量)
在這個系列文章的前面部分,我們談到的很多有關設計的高級別的知識(例如如何拆分內容,並行下載等等),並且大量討論到了腳本、樣式表、圖片的一些優化設計。這一篇文章我們要來討論的是頁面本身的細節設計:我們應該盡可能地使得頁面的DOM元素數量少一些,這樣有助於減小頁面體積,並且也降低了維護這份DOM樹的成本。
什么是DOM?
好吧,如果你不太清楚這個概念,也沒有什么大不了的。DOM的全稱為:Document Object Model ,中文翻譯過來叫文檔對象模型。我們這里所探討的DOM,其實有一個隱含的意思是指HTML DOM。關於它的定義,可以參考下面這個鏈接
http://www.w3school.com.cn/htmldom/index.asp
- HTML DOM 定義了訪問和操作 HTML 文檔的標准方法。
- DOM 以樹結構表達 HTML 文檔。
從上面的定義中,我們可以知道HTML文檔的結構本身就是有一套規范的(例如可以有哪些節點,必須有哪些節點等等),而且對於HTML文檔的訪問也是有規范的(例如要想改變某個元素的位置,則需要修改left,或者top屬性),這套規范就是DOM。這是由W3C確定,並且在所有主流瀏覽器中都共同遵守的一套標准。http://www.w3.org/TR/DOM-Level-2-Core/introduction.html
什么是DOM樹?
實際上並不真的存在DOM樹,這只是我們程序員對於DOM的一種理解方式。一個HTML文檔,由於其獨有的特性,它有且只能有一個根元素,所有其他元素都是根元素的子元素,然后子元素又可以有子元素。對於這種數據結構,為了便於構造以及日后的訪問(包括查詢、修改),我們會采用一種樹形結構來表示它。DOM樹從邏輯上說大致上像下面這樣
【備注】該截圖來自於http://www.w3school.com.cn/htmldom/index.asp
如果有了上述的概念,那么對於“DOM元素應該盡量少”這條原則應該是不難理解的。問題的關鍵在於
- 多少才算少
- 如何減少
多少才算少?
很抱歉,這是一個沒有標准答案的問題。沒有誰規定我們的頁面必須要少於某個數量的DOM元素。雅虎的團隊當年聲稱他們的主頁只有700個元素(對於一個門戶頁面來說,這個真的算很少了),但是最近我再去看這個頁面,我發現目前有1527個元素。
我隨意地打開另外幾個門戶網站(例如新浪)的主頁,發現他們的元素數量就大大增加了。(而且也有很多錯誤)
我們也可以再來看一下博客園的主頁,我發現他們的元素數量也在一個較小的級別。(1265)
所以,對於這個元素數量的問題,並沒有什么固定的標准,應該盡可能地減小。當然,我們完全可以給自己一個小小的目標,例如1000左右?
如何減少DOM元素的數量?
我覺得有幾個方面可以用來減少DOM元素的數量
- 避免不正確地使用服務器控件。
- 減少不必要的內容(並不是所有內容都必須放在頁面上面的)
- 如果數據量大,可以考慮分頁,或者按需加載
避免不正確地使用服務器控件
這個問題被一次又一次地討論(甚至是爭論),ASP.NET給我們帶來的服務器控件,從一開始誕生之日起,就充滿了爭議。服務器控件毫無疑問是簡化了開發過程,因為通過拖拽就能實現復雜的功能。但服務器控件的代價也是相當大的(例如臃腫的代碼,以及視圖狀態),並且從一開始就最被人詬病的是,因為服務器控件隱藏了很多細節,使得有一批網頁的開發人員,只了解服務器控件,甚至連HTML的一些基礎知識都不了解。
作為從ASP時代就開始做網站的人來說,包括我在內,我親身經歷了ASP.NET的整個發展過程。毋庸諱言,實際上微軟也一直在改進ASP.NET。站在今天這樣的時間節點,我個人給出的建議是
- 如果能用ASP.NET MVC做的,就不要用ASP.NET Web Forms。(關於他們各自的優缺點,可以參考這篇文章。)
- 我們不想用ASP.NET Web Forms的原因不光是不想用服務器控件,而且是希望有更好的架構,來支持大型團隊和項目的開發。
- 如果要用ASP.NET Web Forms,要慎重地使用服務器控件。尤其是一些復雜控件內部。
- 大部分時候,我們都可以通過禁用視圖狀態來減小頁面體積。
我們可以來看一個簡單的例子。下面有一個頁面,我們用一個表格來顯示數據。注意,這里使用的是Repeater,而不是DataGrid或者GridView這一類更加復雜的控件。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2.Default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <asp:Repeater ID="data" runat="server"> <ItemTemplate> <tr> <td> <asp:Label runat="server" Text='<%# Eval("ID") %>'></asp:Label></td> <td> <asp:Label runat="server" Text='<%# Eval("FirstName") %>'></asp:Label></td> <td> <asp:Label runat="server" Text='<%# Eval("LastName") %>'></asp:Label></td> <td> <asp:Label runat="server" Text='<%# Eval("Company") %>'></asp:Label></td> <td> <asp:Label runat="server" Text='<%# Eval("Title") %>'></asp:Label></td> </tr> </ItemTemplate> <HeaderTemplate> <table border="1"> <tr> <th>ID</th> <th>FirstName</th> <th>LastName</th> <th>Company</th> <th>Title</th> </tr> </HeaderTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater> </div> </form> </body> </html>
后台代碼很簡單,我只是實例化了1000個數據,然后將其綁定而已。
using System; using System.Linq; namespace WebApplication2 { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //這里只是隨機地綁定了1000行數據 data.DataSource = Enumerable.Range(1, 1000).Select(x => new { Id = x, FirstName = "ares", LastName = "chen", Company = "microsoft", Title = "SDE" }); data.DataBind(); } } } }
頁面運行起來之后,我們可以檢測到它會有11016個元素。
你感到詫異嗎?為什么會有這么多元素呢?我們來看看頁面到底是如何構造控件的吧。首先,在頁面的聲明語句中,加入Trace=true這個屬性
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2.Default" Trace="true"%>
然后在瀏覽器中向頁面底部滾動,就可以看到一些跟蹤信息
我們可以很清楚地發現,為了構造得到一行數據,其實會有12個控件。其最終生成的HTML內容為
那么,如何改進這一點呢?看看下面的代碼
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2.Default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <asp:Repeater ID="data" runat="server"> <ItemTemplate> <tr> <td> <%# Eval("ID") %></td> <td> <%# Eval("FirstName") %></td> <td> <%# Eval("LastName") %></td> <td> <%# Eval("Company") %></td> <td> <%# Eval("Title") %></td> </tr> </ItemTemplate> <HeaderTemplate> <table border="1"> <tr> <th>ID</th> <th>FirstName</th> <th>LastName</th> <th>Company</th> <th>Title</th> </tr> </HeaderTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater> </div> </form> </body> </html>
然后我們再來看頁面中有多少元素呢?6016個。比剛才足足少了5000個。
那么到底少了什么呢?請參考下圖,對照一下前面的截圖,我想你應該會明白的。
現在還有6016個元素,但其實還可以進一步優化,例如將下面紅色的幾行去掉,並且為服務器控件禁用視圖狀態。(在當前這個頁面中,其實只是顯示數據,用不着做提交的)
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2.Default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <asp:Repeater ID="data" runat="server" EnableViewState="false"> <ItemTemplate> <tr> <td><%# Eval("ID") %></td> <td><%# Eval("FirstName") %></td> <td><%# Eval("LastName") %></td> <td><%# Eval("Company") %></td> <td><%# Eval("Title") %></td> </tr> </ItemTemplate> <HeaderTemplate> <table border="1"> <tr> <th>ID</th> <th>FirstName</th> <th>LastName</th> <th>Company</th> <th>Title</th> </tr> </HeaderTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater> </div> </form> </body> </html>
這樣又可以少掉幾個元素。
是不是躍躍欲試了呢?不要着急,我們再來談一個問題:這個頁面上的1000行數據真的有必要進行一次性的加載和顯示嗎?答案通常是否定的,因為瀏覽器的尺寸本來就是有限的,對於用戶來說,並不可能一次性閱讀1000行數據。所以,我們需要了解如何通過分頁或者按需加載的技術,來減少頁面DOM元素的數量,提高加載和維護的效率。
使用分頁加載內容
分頁就是說,雖然數據很多,但我每次只顯示一部分(例如20行),用戶如果想看其他的行,則通過相應的按鈕來導航切換。我們可以將上面的例子稍微改動一下
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2.Default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <asp:Repeater ID="data" runat="server" EnableViewState="false"> <ItemTemplate> <tr> <td><%# Eval("ID") %></td> <td><%# Eval("FirstName") %></td> <td><%# Eval("LastName") %></td> <td><%# Eval("Company") %></td> <td><%# Eval("Title") %></td> </tr> </ItemTemplate> <HeaderTemplate> <table border="1"> <tr> <th>ID</th> <th>FirstName</th> <th>LastName</th> <th>Company</th> <th>Title</th> </tr> </HeaderTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater> <a href='default.aspx?p=<%= CurrentPageIndex+1 %>'>下一頁</a> </body> </html>
作為演示目的,這里只是添加了一個鏈接,點擊可以進入下一頁。服務端代碼也需要稍作修改
using System; using System.Linq; namespace WebApplication2 { public partial class Default : System.Web.UI.Page { public int CurrentPageIndex { get; set; } protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //這里檢測是否要顯示特定頁面 var p = Request.QueryString["p"]; var index = 0; if (string.IsNullOrEmpty(p) || !int.TryParse(p, out index)) CurrentPageIndex = 1; else CurrentPageIndex = index; //這里只是隨機地綁定了20行數據 data.DataSource = Enumerable.Range((CurrentPageIndex - 1) * 20 + 1, 20).Select(x => new { Id = x, FirstName = "ares", LastName = "chen", Company = "microsoft", Title = "SDE" }); data.DataBind(); } } } }
如果用戶沒有提供p這個參數(或者是不正確的值),則默認顯示第一頁。如果提供了,則顯示他想要的頁面。每頁顯示20行。這個頁面顯示出來,只需要133個元素。如下圖所示
當用戶點擊“下一頁”的時候,實際上是一個新的請求。而且同樣只需要133個元素。
對於分頁,還有一些細節直接研究,並且也有一些現成的插件可以使用。例如 http://www.bing.com/search?setmkt=en-US&q=jquery+paging
按需加載內容
分頁可以很好地解決大數據的問題。但由於分頁需要用戶額外的點擊操作,對於用戶來說,可能不是很方便。為了進一步提高用戶體驗,我們是否能做到:
- 默認顯示20行
- 當用戶往下滾動的時候,根據需要再顯示另外20行
- 這是一個循壞
現實世界中,有很多這樣的例子,例如本文前面提到的雅虎主頁,目前就是這樣做的。還有國內比較火的新浪微博,也是這樣做的。
按需加載!聽起來很有點意思吧,由於講解這個做法,相對來說篇幅較大。我希望大家可以自行參考一下下面這篇文章
Load Data From Server While Scrolling Using jQuery AJAX
http://www.codeproject.com/Articles/239436/Load-Data-From-Server-While-Scrolling-Using-JQuery
按需加載與分頁是有根本區別的:分頁之后頁面的體積能夠固定下來,而按需加載的做法中,頁面體積是動態添加,而且也正因為是動態添加到,每次添加的內容有限,所以給用戶的影響很小。
正確地使用JQuery
本文的最后部分,我要特別說明:我在之前的很多演示中都用到過jquery。(目前為止,它確實也是最好的一個javascript庫,沒有之一),但是對於jQuery,越來越多的人在學習,越來越多的人在濫用。這確實也是一個趨勢。
關於如何正確地使用jQuery,國外和國內都有熱心的網友做了總結,請參考