創建人:
@鄭昀
更新日期:2013年5月8日 分類:
前端技術
優化的重要指標:
- 頁面打開速度(Fully Loaded)
- 網站首頁(或列表頁)之 First View :打開速度應在 3秒+0.5秒 內;
- 對 Repeat View 時的各項指標暫不作要求;
- 首屏打開時間(Start Render)
- 網站首頁(或列表頁) 之 First View :首屏渲染速度應在 1秒+0.5秒 內;
- 文檔解析完畢時間(Document Complete):
- 對此指標暫不作要求。
指標測試方法參考附錄A。
提綱:
- 遵循常規優化建議
- 外聯內聯js/css的位置擺放建議
- combo handler的引入
- 圖片無損壓縮的優化
- 減少 dom elements 的數量
- 引入 textarea/script 元素做延遲解析異步渲染
優化第一階段:遵循常規優化建議
本階段所使用工具參考附錄B。
常規優化建議:
- javascript 外聯文件引用放在 html 文檔底部:具體如何擺放內聯JS/CSS和外聯JS/CSS,請參考 優化第二階段;
- css 外聯文件引用在 html 文檔頭部:位於 header 內;
- http 靜態資源盡量用多個子域名:充分利用現代瀏覽器的多線程並發下載能力。瀏覽器的多線程下載能力,參考 附錄C;
- 具體建議:
- JS、CSS、CSS背景圖片、CSSSprite圖片分散在 s0~s2.55tuanimg.com 下;
- 業務類圖片分散在 p0~p3.55tuanimg.com 下;
- 服務器端提供 html 文檔和 http 靜態資源時,盡量開啟 gzip 壓縮;
- json/xml 等格式的文本響應,也建議開啟 gzip ;
- jepg/png 等圖片,可以選擇不開啟 gzip,因為可能服務器端圖片無損壓縮算法已經很強悍了,不需要依賴於 gzip;
- 在 js、css、image 等資源響應的 http headers 里,設置 expires、last-modified;
- 含義參考 附錄D;
- 鄭昀實例示范:
- Nginx設置示范:
-
location ~ .*\.(js|css)${
expires 30d;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp)${
expires 1M;
}
- 盡量減少 HTTP Requests 的數量;
- 通過 combo handler 合並 js 和 css 的下載,參考 優化第三階段;
- 本階段請盡量減少外聯 js/css 文件數,盡量減少 ajax 調用;
- js/css 的 minify:可統一通過 combo handler 做到壓縮加合並;
- 減少不必要的 301/302 跳轉:別讓頁面打開時間浪費在302多次跳轉上(每次可能幾十毫秒);
- 請大量使用 CSS Sprites:這樣做可以大大地減少CSS背景圖片的HTTP請求次數;
- 首屏不需要展示的較大尺寸圖片,請使用 LazyLoad ;
- 避免404錯誤:請求一個外聯 js 失敗時獲得的404錯誤,不但會堵塞並行的下載,而且瀏覽器會嘗試分析404響應的內容,來辨識它是否是有用的Javascript代碼;
- 減少 cookies 的大小:盡量減少 cookies 的體積對減少用戶獲得響應的時間十分重要;
- 去除不必要的 cookie ;
- 盡量減少 cookie 的大小;
- 留心將 cookie 設置在適當的域名下,避免影響到其他網站;
- 設置適當的過期時間。一個較早的過期時間或者不設置過期時間會更快地刪除 cookie,從而減少響應時間。
- 使用無 cookies 的域:
- 當瀏覽其請求一個靜態圖片並一同發送 cookie 時,服務器並不需要這些 cookies 。這樣只是毫無益處地創建了多余的網絡流量。應當保證靜態資源在請求時沒有攜帶 cookies,所以需要把你的靜態資源放在另一個子域名下。
- 如果你的域名是 www.example.org,你可以將你的靜態資源放在 static.example.org 中。如果你把 cookie 設置在頂級域名 example.org 上而不是 www.example.org,那么所有發送至 static.example.org 的請求會包括那些 cookies。在 這種情況下,你可以用一個全新的沒有 cookie 的域名來放置你的靜態資源。
- 避免使用 javascript 來定位布局;
優化第二階段:外聯內聯js/css的位置擺放建議
玉伯定義的加載和阻塞三定律如下:
- 定律一:資源是否下載依賴 JS 執行結果——JS 有可能會修改 DOM。典型的,可能會有
document.write
。這意味着,在當前 JS 加載和執行完成前,后續所有資源的下載有可能是沒必要的。這是 JS 阻塞后續資源下載的根本原因。 - 定律二:JS 執行依賴 CSS 最新渲染——JS 的執行有可能依賴最新樣式。比如,可能會有
var width = $('#id').width()
. 這意味着,JS 代碼在執行前,瀏覽器必須保證在此 JS 之前的所有 css(無論外鏈還是內嵌)都已下載和解析完成。這是 CSS 阻塞后續 JS 執行的根本原因。 - 定律三:現代瀏覽器存在 prefetch 優化—— 現代瀏覽器在競爭中,在 UI update 線程之外,還會開啟另一個線程,對后續 JS 和 CSS 提前下載(注意,僅提前下載,並不執行)。有了 prefetch 優化,這意味着,在不存在任何阻塞的情況下,理論上 JS 和 CSS 的下載時機都非常優先,和位置無關。
根據三定律,
鄭昀認為:
一,如果真的需要把外聯js和css放在head里,那也需要從下面這種排序
1. 外聯js
2. 外聯css
3. 外聯js
統一為:
1. 外聯css
2. 外聯js
3. 外聯js
不要script和css交替混編。
原因是,據有人稱:『
從Firefox 4+開始,對prefetch的策略有些許調整,調整在於 head 中 css 與 js 的位置。
css 在外聯 js 后面時,可能會在 script 后面的 css 加載好之前,提前進行首次渲染。
然后等后面的 css 加載好后,再次更新 Render Tree 並進行渲染,造成頁面閃爍現象。
即,各種優化策略,似乎隨着瀏覽器版本不同,頭可能發生細微差異。
』
所以鄭昀認為,保守做法是,js 和 css 不要在 head 里交替混編,統一為先外聯css再外聯js!
二,但只有萬不得已時,才會在 head 里放外聯js,否則請把外聯js放置到</body>前。
原因是張克軍的《js和css的順序關系》指出:
只要 head 里出現外聯js,無論如何放,css文件都不能和body里的請求並行。
body 里 dom 渲染取決於 head 里的js執行完。
外聯js放在頁面最后,高級瀏覽器會自動做優化(prefetch),你不用擔心,它也可能會提前下載。
優化第三階段:combo handler 的引入
背景
Combo Handler 是 Yahoo! 開發的一個 Apache 模塊,它實現了開發人員簡單方便地通過URL來合並JavaScript和CSS文件,從而大大減少文件請求數。
目的
它滿足 Yahoo! 前端優化第一條原則:Minimize HTTP Requests,來減少三路握手和HTTP請求的發送次數。
國內實例
淘寶網首頁meta里多個js合並的聲明:
<script src="http://a.tbcdn.cn/??s/kissy/1.2.0/kissy-min.js,p/global/1.0/global-min.js,p/fp/2012/core.js,p/fp/2012/fp/module.js,p/fp/2012/fp/util.js,p/fp/2012/fp/directpromo.js?t=2012062320120712.js" data-fp-timestamp="20120703"></script>
js之間用英文逗號或&符號分隔。此src的Response是多個js文件的內容拼裝。
國內的 Combo Script 支持
淘寶李晶-拔赤在 https://github.com/jayli/combo 下發布了combo.php和minfy.php,能夠做到合並文件(不壓縮),以及合並且壓縮。
文件列表:
- combo.php 合並文件,不壓縮
- minify.php 合並壓縮文件
- cssmin.php 壓縮css
- jsmin.php 壓縮js
- cb.php 淘寶CDN合並文件策略的模擬
腳本使用:
- 要求php5及以上版本
- 程序在找不到本地文件的情況下,會去指定的cdn上找同名文件
- 程序會自動轉義-min文件為源文件,因此要約定-min文件和原文件要成對出現
- 需要定義combo.php和minify.php中的$YOUR_CDN變量
- 如果只是合並壓縮local文件,則不必重置$YOUR_CDN變量
- 這里提供cb.php,用來實現tbcdn的開發環境的模擬,apache的配置在cb.php中
CDN上的 Combo Handler支持
1)2008年7月YUI Team宣布在YAHOO! CDN上對YUI JavaScript組件提供Combo Handler服務。
2)淘寶CDN支持Combo Handler,用逗號分隔js/css,用兩個問號來觸發combo特性:
- http://a.tbcdn.cn/??1.js,2.js
- http://a.tbcdn.cn/subdir/??1/js,2.js
用一個問號來添加時間戳,如:
- http://a.tbcdn.cn/??fp/directpromo.js?t=2012062320120712
為了避免 CDN 緩存錯誤的版本,combo上線的訪問策略是:
1)靜態文件傳到服務器端;
2)部署人員使用線上靜態文件服務器的IP地址直接請求combo服務,挨個兒combo請求一次;
3)部署人員確認上面的請求都成功、內容無誤后,再換成CDN地址再次請求,確保CDN緩存正確的文件內容。
優化第四階段:圖片無損壓縮的優化
下面的建議來自於馮凱。
由於專賣店等各種業務上傳的圖片有 jpeg、png 和 gif 等格式,因此三種格式都需要優化:
1)jpegtran和jpegoptim的壓縮效果幾乎完全相同。
但jpegtran有progressive編碼(漸進式的展示,先顯示模糊的,再逐步清晰),而且通常(84%的概率)對於大圖片(10k+)壓縮比更高。
雖然我們的大部分頁面已經改成延遲加載了,但對於非延遲加載的頁面,效果明顯更好。
經測試,pagespeed 並沒有按照 progressive 方式提供建議。
2)測試了png的幾種壓縮方式,壓縮效果各異。測試的一張圖片 optipng 只壓縮了約5%,但其他幾種達到了20%+
經測試,pagespeed上給出的可壓縮比例是按照optipng給出的。
pngout據說采用了不同的編碼,因此對小圖片壓縮效果更好。用imageoptim測試確實略優,但命令行上還沒找到合適的調用參數。
目前決定采用pngcrush。
對於采用png8,以大幅壓縮的方法,我們不做技術處理。
3)gif就采用gifsicle做壓縮。
大部分情況下,我們不建議采用gif圖片。對於單幀gif更應該用png格式替代。
這里我們暫不考慮通過技術處理來吧單幀gif轉換成png。
4)采用php的exec調用shell腳本的方式來執行這些bin文件。
參考資料:
2011年時,我們首頁一個商品節點包含了
21個DOM節點,充滿了大量的em、strong、span。
所以前端開發部門必須與產品部交互設計人員積極溝通,而不僅僅是在她們提供的交互設計稿件上切圖,必須在簡化視覺元素和精簡DOM節點上表達自己的意見。
2012年,簡化設計后,首頁一個商品節點包含
13個節點。
優化第六階段:引入 textarea/script 元素做延遲解析異步渲染
textarea 延遲渲染原理
據
玉伯介紹,HTML 元素中有一種 RCDATA elements,含 textarea 和 title 。
RCDATA指的是,Replaceable Character Data。
如果用隱藏的 textarea 來存放 html 代碼,textarea 中的內容會按照 RCDATA 規則來解析:
- 遇到 & 時,會盡可能得到實體字符。
- 遇到 </textarea(\s|\\|>) 時,會結束解析。
- 其他都直接作為 textarea 的內容。

獲取也非常簡單:

據
yiminghe介紹,
對於屏幕外延遲渲染的 html 存放在隱藏(visibility:hidden)的 textarea 中,並且該 textarea 占據本該渲染的位置,監控窗體滾動,當textarea進入可見區域,將該 textarea 內的 value, 插入到 textarea 之前,並刪除掉 textarea 。
這樣,把大量不需要在首屏展示的html代碼分模塊放入一個一個的 textarea 里,
大大減少了DOM節點數,從而給瀏覽器合理的喘息(UI Update)時間,
等首屏真正在顯示器上繪制出來后,再得到 textarea.value ,填充回 DOM Tree。
textarea+datalazyload,相對於其他延遲加載異步渲染解決方案,最大好處,還是減少首屏繪制時的DOM節點總數。
參考資料:
- 玉伯(王保平,id@lifesinger)《淘寶詳情頁的 BigRender 優化與存放大塊 HTML 內容的最佳方式》(需翻牆)
- BigRender所依賴的“數據延遲加載組件”datalazyload
- yiminghe《數據延遲加載組件》
script 延遲渲染原理
玉伯在《
淘寶詳情頁的 BigRender 優化與存放大塊 HTML 內容的最佳方式》中提到,
與前面說的 textarea 存放 html 代碼一樣,你也可以用 script 來存放,目的都是減少 DOM 節點數。
瀏覽器在拿到 html 代碼時,首次 Tokenization — Tree Construction 的速度就會大大加快。

某網是怎么實踐的
在某網商品詳情頁上,HTML 文檔底部遍布着這樣的代碼:

注意這些 script 的 type 是 text/x-template ,這是YUI類庫自己定義的元素type。
你可以注意到,LABjs 也玩過這個小技巧,也是自己定義了一個元素 type“text/cache”, 由於瀏覽器不認識這種 type,就會主動忽略這個 HTML元素。
什么時候取出這些隱藏HTML代碼呢?
那就要用到這些 script 的 id 了。
YUI的教程上是這么獲得 HTML 代碼:
template: Y.one('#todo-item-template').getHTML(),
某網的做法是:


某網由於走的是 YUI3.0 體系,所以可以利用 script 存放html代碼技巧,讓商品詳情頁首屏更快地渲染出來。
我們的實踐
在商品詳情頁上,我們把很多不需要首屏渲染的 html 代碼放入了類似於
<textarea id="goodsAll_info" style="display:none;"></textarea>
的隱藏 textarea 里了。
然后在 html 文檔底部,放內聯 js 來讀取:
<script type="text/javascript">
$(function(){
var area = document.getElementById("goodsAll_info").value;
document.getElementById("goodsAll_info_div").innerHTML = area;
});
</script>
附錄A:頁面打開速度和首屏打開時間的測量
推薦工具:
- 推薦使用 http://www.webpagetest.org/ 評測,由於它受限於並發測試和帶寬,所以資源下載速度較差,只能作為與競爭對手對比測試的依據;
- Test Location 請選擇亞洲的中國江蘇節點;
- Browser 請選擇 Firefox、IE9、Chrome等現代瀏覽器;
- 如下圖所示:
- 鄭昀
- 運維部的每周博睿檢測數據報告,我們以博睿的數據為准;
- 博睿從它的各地監測節點以及不同電信鏈路訪問,得到一個響應速度的平均值;
- Google PageSpeed https://developers.google.com/speed/pagespeed/insights 的 Critical Path Explorer;
附錄B:能提出常規優化建議的工具
推薦工具:
- Firefox插件 YSlow! ;
- Google PageSpeed https://developers.google.com/speed/pagespeed/insights ;
附錄C:瀏覽器多線程下載能力一覽
參考怪飛的文章《
各瀏覽器的並行連接數(同域名) 》:
Browser | HTTP/1.1 | HTTP/1.0 |
IE 6,7 | 2 | 4 |
IE 8 | 6 | 6 |
Firefox 3+ | 6 | 6 |
Safari 3+ | 4 | 4 |
Chrome 3+ | 4 | 4 |
Chrome 11+ | 6 | ? |
Opera 10+ | 4 | 4 |
Opera 11+ | 16 | ? |
附錄D:expires和last-modified概念
1)Expires
給出的日期/時間后,被響應認為是過時。如Expires: Thu, 02 Apr 2009 05:14:08 GMT
需和Last-Modified結合使用。用於控制請求文件的有效時間,當請求數據在有效期內時客戶端瀏覽器從緩存請求數據而不是服務器端. 當緩存中數據失效或過期,才決定從服務器更新數據。
2)Last-Modified和Expires
Last-Modified標識能夠節省一點帶寬,但是還是逃不掉發一個HTTP請求出去,而且要和Expires一起用。而Expires標識卻使得瀏覽器干脆連HTTP請求都不用發,比如當用戶F5或者點擊Refresh按鈕的時候就算對於有Expires的URI,一樣也會發一個HTTP請求出去,所以,Last-Modified還是要用的,而 且要和Expires一起用。
贈圖一枚:
@正和島標准 :【看圖】做事感覺特別困難的時候,可能收獲也會特別巨大。

@烈火在線:每次看產品經理發來的文檔,都是這種感覺。。。
