網站設計的優化是一個很大的話題,有一些通用的原則,也有針對不同開發平臺的一些建議。這方面的研究一直沒有停止過,我在不同的場合也分享過這樣的話題。
作爲通用的原則,雅虎的工程師團隊曾經給出過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,國外和國內都有熱心的網友做了總結,請參考