前言
網站設計的優化是一個很大的話題,有一些通用的原則,也有針對不同開發平台的一些建議。這方面的研究一直沒有停止過,我在不同的場合也分享過這樣的話題。
作為通用的原則,雅虎的工程師團隊曾經給出過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的開發基本流程和核心技術有相當的了解,本系列文章很難對基礎知識做普及。
本文要討論的話題
這一篇我和大家討論的是第十六條原則:Postload Components (延遲或按需加載內容)
頁面加載過程中,除了頁面本身的內容之外,可能需要加載很多額外的資源,例如我們常說的:
- 腳本
- 樣式表
- 圖片
我在之前的文章中,已經有針對腳本和樣式表做了一些優化的建議,請參考
這一條原則的核心是:延遲或按需加載。首先來針對我們比較最經常用到的腳本為例進行講解。
針對腳本的按需加載
我們可以想象一下,一個真正的網站項目中,會有各種各樣的腳本文件,其中還包含很多基礎的框架(例如jquery,knockoutjs 等),這些腳本文件可能都或多或少需要在頁面中引用。問題在於,如果頁面一多起來,或者復雜起來,我們可能不太能准確地知道某個頁面是否真的需要某個腳本。(難道不是這樣嗎?),一個蹩腳的解決方案是,那么就在母版頁中,一次性將所有可能用到的框架腳本都引用進來吧。你是這樣做的嗎?
如果你真的這樣做,那么,可能可以一時地解決問題。但實際上存在一個問題,在某些頁面上,可能只用到一個腳本庫,但為了你的方便,以后都需要全部加載所有的腳本庫了。
隨着項目的進一步開發,腳本之間的依賴會進一步復雜,要維護這些腳本確實是一個大問題。
在當年雅虎的團隊寫下這條原則的時候,他們提到了一個他們自己開發的組件來實現按需加載腳本,這個組件叫做GET,是包含在YUI這套工具包中的。http://yuilibrary.com/yui/docs/get/ ,它的意思就是可以動態地,按需加載腳本和樣式表。
我對YUI的研究並不太多,而實際上這幾年,Javascript這方面的技術突飛猛進,涌現了更多的創新性的設計。例如我今天要講的requirejs。
我通過一個簡單的例子給大家來講解吧
這里有一個簡單的網站,首頁叫Default.aspx。根據我們的設計,這個頁面需要加載jquery,以及可能的其他一些庫,然后執行自己的一些邏輯。所以,我們會有如下的腳本引用
<!--傳統的做法中,我們需要在頁面中添加所有的腳本引用,有時候可能會加載一些不必要的腳本--> <script src="Scripts/jquery-2.0.0.min.js"></script> <script src="Scripts/knockout-2.2.1.js"></script> <script src="Scripts/default.js"></script>
這樣做有什么問題嗎?當然不是。只不過如我們之前所談到的那樣,這種預先加載所有腳本的方式,可能造成資源的浪費,而且這么多腳本引用在頁面中,很容易引起混淆。為了更好地說明這一點,我給大家演示一個真實的場景:
- 我們希望頁面在加載的時候,只下載jquery這個庫
- 只有當用戶點擊了頁面上面的那個按鈕,我們才去下載knockout這個庫
瞧!這就是所謂的按需加載。那么來看看我們將如何使用requirejs實現這個需求吧?
首先,你可以通過Nuget Package Manager 獲取requirejs這個庫。
然后,在頁面中,你只需要像下面這樣定義腳本引用。(以后,你的頁面中也只需要有這樣一個引用)
<script src="Scripts/require.js" data-main="scripts/default"></script>
這里的data-main指的是主腳本。require.js會首先下載的一個腳本。你確實可以寫成下面這樣
<script src="Scripts/require.js" data-main="scripts/default.js"></script>
但是,正如你所見,.js是可以省略掉的。
接下來在default.js中,應該如何寫腳本呢?下面是一個簡單的例子
/// <reference path="require.js" /> /// <reference path="jquery-2.0.0.js" /> /// <reference path="knockout-2.2.1.js" /> //對requirejs進行一些基本配置 requirejs.config({ paths: { jquery: "jquery-2.0.0.min" //指定一個路徑別名 , knockout: "knockout-2.2.1" } }); //聲明下面的代碼是需要jquery這個庫的 require(['jquery'], function () { $(function () { alert("Hello,jquery!"); }); });
我們看到,第一部分是對requirejs的基本配置,我們定義了兩個別名。然后在第二部分,我們聲明了下面的代碼是需要jquery這個庫的。
將頁面運行起來之后,在瀏覽器中我們可以監控得到腳本下載的行為如下
如我們設想的那樣,先加載了require.js,然后加載了default.js, 然后才是加載了jquery.js
有點意思,不是嗎?雖然最后的結果也是加載了jquery,但這個加載方式與直接在頁面中定義引用有着本質的區別,這是按需加載的。如果你對此還不太贊同,那么看了下面的例子,我相信你一定會同意的。
我們需要在default.js這個文件中,為頁面上的那個按鈕訂閱點擊事件,而且我們希望,只有當用戶真的點擊過了按鈕,才會下載另外一個腳本(knockout),看看如何實現這個需求吧?
/// <reference path="require.js" /> /// <reference path="jquery-2.0.0.js" /> /// <reference path="knockout-2.2.1.js" /> //對requirejs進行一些基本配置 requirejs.config({ paths: { jquery: "jquery-2.0.0.min" //指定一個路徑別名 , knockout: "knockout-2.2.1" } }); //聲明下面的代碼是需要jquery這個庫的 require(['jquery'], function ($) { $(function () { //只有用戶點擊了某個按鈕,才動態加載knockoutjs $("#test").click( function () { require(['knockout'], function (ko) { alert(ko.version); }); } ); }); });
同樣的,我們可以通過瀏覽器監控工具了解腳本下載的流程:
頁面加載的時候,仍然只有三個腳本下載了
但是,如果點擊了按鈕,則會有第四個腳本下載
同時,從下面的對話框來看,也可以斷定確實是執行了相應的腳本的。因為我們當前使用的knockout腳本的版本確實是2.2.1。
這的確是一個很不錯的機制。如果大家有興趣,還可以繼續深入研究,現在jquery為了更好地滿足動態加載和按需加載的需要,甚至都提供了模塊化的版本。請參考 http://projects.jga.me/jquery-builder/
針對樣式表文件的按需加載
我相信按需加載腳本文件這樣的設計,足夠引起你的興趣了。很自然地,你可能會有這樣一個問題,能不能實現對樣式表的按需加載呢?
聽起來不錯,而且應該也不難,但目前沒有現成的實現。當然YUI中的GET是可以用的。
requirejs的官方有一個解釋,有興趣可以參考一下 http://requirejs.org/docs/faq-advanced.html#css
他們也提供了一個建議的腳本來按需加載樣式表
function loadCss(url) { var link = document.createElement("link"); link.type = "text/css"; link.rel = "stylesheet"; link.href = url; document.getElementsByTagName("head")[0].appendChild(link); }
你可以將這個腳本訪問任何的地方,進行調用。例如我是下面這樣做的
/// <reference path="require.js" /> /// <reference path="jquery-2.0.0.js" /> /// <reference path="knockout-2.2.1.js" /> //對requirejs進行一些基本配置 requirejs.config({ paths: { jquery: "jquery-2.0.0.min" //指定一個路徑別名 , knockout: "knockout-2.2.1" } }); //聲明下面的代碼是需要jquery這個庫的 require(['jquery'], function ($) { $(function () { //只有用戶點擊了某個按鈕,才動態加載knockoutjs $("#test").click( function () { loadCss('default.css'); require(['knockout'], function (ko) { alert(ko.version); }); } ); }); }); function loadCss(url) { var link = document.createElement("link"); link.type = "text/css"; link.rel = "stylesheet"; link.href = url; document.getElementsByTagName("head")[0].appendChild(link); }
針對圖片的按需加載
本文的最后一部分我們來談談圖片的按需加載的問題。如果你的頁面包含了大量的圖片,掌握這個原則將非常有助於提高網頁的加載速度。
大家可以設想一下圖片搜索引擎的結果頁面,例如 https://www.google.com/search?newwindow=1&site=&tbm=isch&source=hp&biw=1468&bih=773&q=microsoft&oq=microsoft&gs_l=img.3...1779.4076.0.4399.9.7.0.0.0.0.0.0..0.0...0.0...1ac.1j4.12.img.aajYF7y8xW8
我在images.google.com中搜索microsoft,毫無疑問,這會返回成千上萬張圖片。
那么,這些圖片是不是要一次性全部加載進來呢?顯然不可能,你可能會說,應該做分頁會不會好一些?分頁通常是一個好技術,但在搜索引擎的頁面中,分頁不是一個很好的選擇(作為用戶並不見得願意去點擊頁面導航按鈕),目前主流的是圖片搜索引擎的做法都是不采用分頁,而是隨着用戶的滾動條滑動來按需獲取圖片。
這是一個相當重要的設計,但稍微思考一下,應該不是很簡單的一個工作。幸運的是,雅虎的團隊提供了一個很好的組件(ImageLoader)可以直接使用:http://yuilibrary.com/yui/docs/imageloader/
關於這個組件的用法,有幾個在線的演示頁面:
- Basic Features of the ImageLoader Utility
- Loading Images Below the Fold
- Using ImageLoader with CSS Class Names
如果你習慣用jquery,那么可以參考一下下面這個說明
http://www.appelsiini.net/projects/lazyload