優化網站設計:減少DOM元素的數量

網站設計的優化是一個很大的話題,有一些通用的原則,也有針對不同開發平臺的一些建議。這方面的研究一直沒有停止過,我在不同的場合也分享過這樣的話題。

作爲通用的原則,雅虎的工程師團隊曾經給出過35個最佳實踐。這個列表請參考  Best Practices for Speeding Up Your Web Sitehttp://developer.yahoo.com/performance/rules.html,同時,他們還發布了一個相應的測試工具Yslow http://developer.yahoo.com/yslow/

我強烈推薦所有的網站開發人員都應該學習這些最佳實踐,並結合自己的實際項目情況進行應用。 接下來的一段時間,我將結合ASP.NET這個開發平臺,針對這些原則,通過一個系列文章的形式,做些講解和演繹,以幫助大家更好地理解這些原則,並且更好地使用他們。

準備工作

爲了跟隨我進行後續的學習,你需要準備如下的開發環境和工具

  1. Google Chrome 或者firefox ,並且安裝 Yslow這個擴展組件.請注意,這個組件是雅虎提供的,但目前沒有針對IE的版本。
    1. https://chrome.google.com/webstore/detail/yslow/ninejjcohidippngpapiilnmkgllmakh
    2. https://addons.mozilla.org/en-US/firefox/addon/yslow/
    3. 你應該對這些瀏覽器的開發人員工具有所瞭解,你可以通過按下F12鍵調出這個工具。
  2. Visaul Studio 2010 SP1 或更高版本,推薦使用Visual Studio 2012
    1. http://www.microsoft.com/visualstudio/eng/downloads
  3. 你需要對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

  1. HTML DOM 定義了訪問和操作 HTML 文檔的標準方法。
  2. DOM 以樹結構表達 HTML 文檔。

從上面的定義中,我們可以知道HTML文檔的結構本身就是有一套規範的(例如可以有哪些節點,必須有哪些節點等等),而且對於HTML文檔的訪問也是有規範的(例如要想改變某個元素的位置,則需要修改left,或者top屬性),這套規範就是DOM。這是由W3C確定,並且在所有主流瀏覽器中都共同遵守的一套標準。http://www.w3.org/TR/DOM-Level-2-Core/introduction.html

什麼是DOM樹?

實際上並不真的存在DOM樹,這只是我們程序員對於DOM的一種理解方式。一個HTML文檔,由於其獨有的特性,它有且只能有一個根元素,所有其他元素都是根元素的子元素,然後子元素又可以有子元素。對於這種數據結構,爲了便於構造以及日後的訪問(包括查詢、修改),我們會採用一種樹形結構來表示它。DOM樹從邏輯上說大致上像下面這樣

DOM HTML TREE

【備註】該截圖來自於http://www.w3school.com.cn/htmldom/index.asp

 

如果有了上述的概念,那麼對於“DOM元素應該儘量少”這條原則應該是不難理解的。問題的關鍵在於

  1. 多少纔算少
  2. 如何減少

 

多少纔算少?

很抱歉,這是一個沒有標準答案的問題。沒有誰規定我們的頁面必須要少於某個數量的DOM元素。雅虎的團隊當年聲稱他們的主頁只有700個元素(對於一個門戶頁面來說,這個真的算很少了),但是最近我再去看這個頁面,我發現目前有1527個元素。

image
我隨意地打開另外幾個門戶網站(例如新浪)的主頁,發現他們的元素數量就大大增加了。(而且也有很多錯誤)

image

我們也可以再來看一下博客園的主頁,我發現他們的元素數量也在一個較小的級別。(1265)

image

所以,對於這個元素數量的問題,並沒有什麼固定的標準,應該儘可能地減小。當然,我們完全可以給自己一個小小的目標,例如1000左右?

 

如何減少DOM元素的數量?

我覺得有幾個方面可以用來減少DOM元素的數量

  1. 避免不正確地使用服務器控件。
  2. 減少不必要的內容(並不是所有內容都必須放在頁面上面的)
    • 如果數據量大,可以考慮分頁,或者按需加載

 

避免不正確地使用服務器控件

這個問題被一次又一次地討論(甚至是爭論),ASP.NET給我們帶來的服務器控件,從一開始誕生之日起,就充滿了爭議。服務器控件毫無疑問是簡化了開發過程,因爲通過拖拽就能實現複雜的功能。但服務器控件的代價也是相當大的(例如臃腫的代碼,以及視圖狀態),並且從一開始就最被人詬病的是,因爲服務器控件隱藏了很多細節,使得有一批網頁的開發人員,只瞭解服務器控件,甚至連HTML的一些基礎知識都不瞭解。

作爲從ASP時代就開始做網站的人來說,包括我在內,我親身經歷了ASP.NET的整個發展過程。毋庸諱言,實際上微軟也一直在改進ASP.NET。站在今天這樣的時間節點,我個人給出的建議是

  1. 如果能用ASP.NET MVC做的,就不要用ASP.NET Web Forms。(關於他們各自的優缺點,可以參考這篇文章。)
    • 我們不想用ASP.NET Web Forms的原因不光是不想用服務器控件,而且是希望有更好的架構,來支持大型團隊和項目的開發。
  2. 如果要用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個元素。

image

你感到詫異嗎?爲什麼會有這麼多元素呢?我們來看看頁面到底是如何構造控件的吧。首先,在頁面的聲明語句中,加入Trace=true這個屬性

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2.Default"  Trace="true"%>
然後在瀏覽器中向頁面底部滾動,就可以看到一些跟蹤信息

image

我們可以很清楚地發現,爲了構造得到一行數據,其實會有12個控件。其最終生成的HTML內容爲

image

那麼,如何改進這一點呢?看看下面的代碼

<%@ 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個。

image

那麼到底少了什麼呢?請參考下圖,對照一下前面的截圖,我想你應該會明白的。

image

現在還有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>

這樣又可以少掉幾個元素。

image

是不是躍躍欲試了呢?不要着急,我們再來談一個問題:這個頁面上的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個元素。如下圖所示

image

當用戶點擊“下一頁”的時候,實際上是一個新的請求。而且同樣只需要133個元素。

image

對於分頁,還有一些細節直接研究,並且也有一些現成的插件可以使用。例如 http://www.bing.com/search?setmkt=en-US&q=jquery+paging

 

按需加載內容

分頁可以很好地解決大數據的問題。但由於分頁需要用戶額外的點擊操作,對於用戶來說,可能不是很方便。爲了進一步提高用戶體驗,我們是否能做到:

  1. 默認顯示20行
  2. 當用戶往下滾動的時候,根據需要再顯示另外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,國外和國內都有熱心的網友做了總結,請參考

  1. http://www.cnblogs.com/huyh/archive/2009/03/30/1422976.html (國內的,翻譯文檔)
  2. http://www.cnblogs.com/huyh/archive/2009/03/31/1425430.html (國內的,翻譯文檔)
  3. http://net.tutsplus.com/tutorials/javascript-ajax/10-ways-to-instantly-increase-your-jquery-performance/ (國外的)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章