很是棒、很是系統的一份資料,值得閱讀!javascript
原文來自百度泛用戶體驗。 php
做者:nwindcss
本文將探討瀏覽器渲染的loading過程,主要有2個目的:html
因爲loading和parsing是相互交織、錯綜複雜的,這裏面有大量的知識點,爲了不過於發散本文將不會對每一個細節都深刻研究,而是將重點放在開發中容易控制的部分(Web前端和Web Server),同時因爲瀏覽器種類繁多且不一樣版本間差距很大,本文將側重一些較新的瀏覽器特性前端
提高頁面性能方面已經有不少前人的優秀經驗了,如Best Practices for Speeding Up Your Web Site和Web Performance Best Practiceshtml5
本文主要專一其中加載部分的優化,總結起來主要有如下幾點:java
接下來就從瀏覽器各個部分的實現來梳理性能優化方法nginx
首先是網絡層部分,這方面的實現大部分是經過調用操做系統或gui框架提供的apigit
爲了應對DNS查詢的延遲問題,一些新的瀏覽器會緩存或預解析DNS,如當Chrome訪問google頁面的搜索結果時,它會取出連接中的域名進行預解析github
固然,Chrome並非每次都將頁面中的全部連接的域名都拿來預解析,爲了既提高用戶體驗又不會對DNS形成太大負擔,Chrome作了不少細節的優化,如經過學習用戶以前的行爲來進行判斷
Chrome在啓動時還會預先解析用戶常去的網站,具體能夠參考DNS Prefetching,當前Chrome中的DNS緩存狀況能夠經過net-internals頁面來察看
爲了幫助瀏覽器更好地進行DNS的預解析,能夠在html中加上如下這句標籤來提示瀏覽器
<link rel="dns-prefetch" href="//HOSTNAME.com">
除此以外還可使用HTTP header中的X-DNS-Prefetch-Control來控制瀏覽器是否進行預解析,它有on和off兩個值,更詳細的信息請參考Controlling DNS prefetching
本文不打算詳細討論這個話題,感興趣的讀者能夠閱讀Content delivery network
在性能方面與此相關的一個問題是用戶可能使用自定義的DNS,如OpenDNS或Google的8.8.8.8,須要注意對這種狀況進行處理
因爲Web頁面加載是同步模型,這意味着瀏覽器在執行js操做時須要將後續html的加載和解析暫停,由於js中有可能會調用document.write來改變dom節點,不少瀏覽器除了html以外還會將css的加載暫停,由於js可能會獲取dom節點的樣式信息,這個暫停會致使頁面展示速度變慢,爲了應對這個問題,Mozilla等瀏覽器會在執行js的同時簡單解析後面的html,提取出連接地址提早下載,注意這裏僅是先下載內容,並不會開始解析和執行
這一行爲還能夠經過在頁面中加入如下標籤來提示瀏覽器
<link rel="prefetch" href="http://">
但這種寫法目前並無成爲正式的標準,也只有Mozilla真正實現了該功能,能夠看看Link prefetching FAQ
WebKit也在嘗試該功能,具體實現是在HTMLLinkElement的process成員函數中,它會調用ResourceHandle::prepareForURL()函數,目前從實現看它是僅僅用作DNS預解析的,和Mozilla對這個屬性的處理不一致
對於不在當前頁面中的連接,若是須要預下載後續內容能夠用js來實現,請參考這篇文章Preload CSS/JavaScript without execution
預下載後續內容還能作不少細緻的優化,如在Velocity China
2010中,來自騰訊的黃希彤介紹了騰訊產品中使用的交叉預下載方案,利用空閒時間段的流量來預加載,這樣即提高了用戶訪問後續頁面的速度,又不會影響到高峯期的流量,值得借鑑
預渲染比預下載更進一步,不只僅下載頁面,並且還會預先將它渲染出來,目前在Chrome(9.0.597.0)中有實現,不過須要在about:flags中將’Web Page Prerendering’開啓
不得不說Chrome的性能優化作得很細緻,各方面都考慮到了,也難怪Chrome的速度很快
在網絡層之上咱們主要關注的是HTTP協議,這裏將主要討論1.1版本,若是須要了解1.0和1.1的區別請參考Key Differences between HTTP/1.0 and HTTP/1.1
首先來看http中的header部分
header的大小通常會有500 多字節,cookie內容較多的狀況下甚至能夠達到1k以上,而目前通常寬帶都是上傳速度慢過下載速度,因此若是小文件多時,甚至會出現頁面性能瓶頸出在用戶上傳速度上的狀況,因此縮小header體積是頗有必要的,尤爲是對不須要cookie的靜態文件上,最好將這些靜態文件放到另外一個域名上
將靜態文件放到另外一個域名上會出現的現象是,一旦靜態文件的域名出現問題就會對頁面加載形成嚴重影響,尤爲是放到頂部的js,若是它的加載受阻會致使頁面展示長時間空白,因此對於流量大且內容簡單的首頁,最好使用內嵌的js和css
header中有些擴展屬性能夠用來保護站點,瞭解它們是有益處的
首先性能因素不該該是考慮使用get仍是post的主要緣由,首先關注的應該是否符合HTTP中標準中的約定,get應該用作數據的獲取而不是提交
之因此用get性能更好的緣由是有測試代表,即便數據很小,大部分瀏覽器(除了Firefox)在使用post時也會發送兩個TCP的packet,因此性能上會有損失
在HTTP/1.1協議下,單個域名的最大鏈接數在IE6中是2個,而在其它瀏覽器中通常4-8個,而總體最大連接數在30左右
而在HTTP/1.0協議下,IE六、7單個域名的最大連接數能夠達到4個,在Even Faster Web Sites一書中的11章還推薦了對靜態文件服務使用HTTP/1.0協議來提升IE六、7瀏覽器的速度
瀏覽器連接數的詳細信息能夠在Browserscope上查到
使用多個域名能夠提升併發,但前提是每一個域名速度都是一樣很快的,不然就會出現某個域名很慢會成爲性能瓶頸的問題
主流瀏覽器都遵循http規範中的Caching in HTTP來實現的
從HTTP cache的角度來看,瀏覽器的請求分爲2種類型:conditional requests 和 unconditional requests
unconditional請求是當本地沒有緩存或強制刷新時發的請求,web server返回200的heder,並將內容發送給瀏覽器
而conditional則是當本地有緩存時的請求,它有兩種:
如下是IE發送conditional requests的條件,從MSDN上抄來
簡單的來講,點擊刷新按鈕或按下F5時會發出conditional請求,而按下ctrl的同時點擊刷新按鈕或按下F5時會發出unconditional請求
須要進一步學習請閱讀:
瀏覽器會盡量地優化前進後退,使得在前進後退時不須要從新渲染頁面,就好像將當前頁面先「暫停」了,後退時再從新運行這個「暫停」的頁面
不過並非全部頁面都能「暫停」的,如當頁面中有函數監聽unload事件時,因此若是頁面中的連接是原窗口打開的,對於unload事件的監聽會影響頁面在前進後時的性能
在新版的WebKit裏,在事件的對象中新增了一個persisted屬性,能夠用它來區分首次載入和經過後退鍵載入這兩種不一樣的狀況,而在Firefox中可使用pageshow和pagehide這兩個事件
unload事件在瀏覽器的實現中有不少不肯定性因素,因此不該該用它來記錄重要的事情,而是應該經過按期更新cookie或按期保存副本(如用戶備份編輯文章到草稿中)等方式來解決問題
具體細節能夠參考WebKit上的這2篇文章:
瀏覽器中對cookie的支持通常是網絡層庫來實現的,瀏覽器不須要關心,如IE使用的是WinINET
須要注意IE對cookie的支持是基於pre-RFC Netscape draft spec for cookies的,和標準有些不一樣,在設定cookie時會出現轉義不全致使的問題,如在ie和webkit中會忽略「=」,不過大部分web開發程序(如php語言)都會處理好,自行編寫http交互時則須要注意
在IE中默認狀況下iframe中的頁面若是域名和當前頁面不一樣,iframe中的頁面是不會收到cookie的,這時須要經過設置p3p來解決,具體能夠察看微軟官方的文檔,加上以下header便可
P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
這對於用iframe嵌入到其它網站中的第三方應用很重要
頁面的編碼能夠在http header或meta標籤中指明,對於沒有指明編碼的頁面,瀏覽器會根據是否設置了auto detect來進行編碼識別(如在chrome中的View-Encoding菜單)
關於編碼識別,Mozilla開源了其中的Mozilla Charset Detectors模塊,感興趣的能夠對其進行學習
建議在http
header中指定編碼,若是是在meta中指定,瀏覽器在獲得html頁面後會首先讀取一部份內容,進行簡單的meta標籤解析來得到頁面編碼,如WebKit代碼中的HTMLMetaCharsetParser.cpp,能夠看出它的實現是查找charset屬性的值,除了WebKit之外的其它瀏覽器也是相似的作法,這就是爲什麼HTML5中直接使用以下的寫法瀏覽器都支持
<meta charset="utf-8">
須要注意不設定編碼會致使不可預測的問題,應儘量作到明確指定
瀏覽器在加載html時,只要網絡層返回一部分數據後就會開始解析,並下載其中的js、圖片,而不須要等到全部html都下載完成纔開始,這就意味着若是能夠分段將數據發送給瀏覽器,就能提升頁面的性能,這就是chunked的做用,具體協議細節請參考Chunked Transfer Coding
在具體實現上,php中能夠經過flush函數來實現,不過其中有很多須要注意的問題,如php的配置、web server、某些IE版本的問題等,具體請參考php文檔及評論
注意這種方式只適用於html頁面,對於xml類型的頁面,因爲xml的嚴格語法要求,瀏覽器只能等到xml所有下載完成後纔會開始解析,這就意味着同等狀況下,xml類型的頁面展示速度必然比html慢,因此不推薦使用xml
即便不使用這種http傳輸方式,瀏覽器中html加載也是邊下載邊解析的,而不需等待全部html內容都下載完纔開始,因此實際上chunked主要節省的是等待服務器響應的時間,由於這樣能夠作到服務器計算完一部分頁面內容後就馬上返回,而不是等到全部頁面都計算都完成才返回,將操做並行
另外Facebook所使用的BigPipe其實是在應用層將頁面分爲了多個部分,從而作到了服務端和瀏覽器計算的並行
keepalive使得在完成一個請求後能夠不關閉socket鏈接,後續能夠重複使用該鏈接發送請求,在HTTP/1.0和HTTP/1.1中都有支持,在HTTP/1.1中默認是打開的
keepalive在瀏覽器中都會有超時時間,避免長期和服務器保持鏈接,如IE是60秒
另外須要注意的是若是使用阻塞IO(如apache),開啓keepalive保持鏈接會很消耗資源,能夠考慮使用nginx、lighttpd等其它web server,具體請參考相關文檔,這裏就不展開描述
pipelining是HTTP/1.1協議中的一個技術,能讓多個HTTP請求同時經過一個socket傳輸,注意它和keepalive的區別,keepalive能在一個socket中傳輸多個HTTP,但這些HTTP請求都是串行的,而pipelining則是並行的
惋惜目前絕大部分瀏覽器在默認狀況下都不支持,已知目前只有opera是默認支持的,加上不少網絡代理對其支持很差致使容易出現各類問題,因此並無普遍應用
SPDY是google提出的對HTTP協議的改進,主要是目的是提升加載速度,主要有幾點:
從實現上看,frame類(包括iframe和frameset)的標籤是最耗時的,並且會致使多一個請求,因此最好減小frame數量
若是要嵌入不信任的網站,可使用這個屬性值來禁止頁面中js、ActiveX的執行,能夠參考msdn的文檔
<iframe security="restricted" src=""></iframe>
對於html的script標籤,若是是外鏈的狀況,如:
<script src="a.js"></script>
瀏覽器對它的處理主要有2部分:下載和執行
下載在有些瀏覽器中是並行的,有些瀏覽器中是串行的,如IE八、Firefox三、Chrome2都是串行下載的
執行在全部瀏覽器中默認都是阻塞的,當js在執行時不會進行html解析等其它操做,因此頁面頂部的js不宜過大,由於那樣將致使頁面長時間空白,對於這些外鏈js,有2個屬性能夠減小它們對頁面加載的影響,分別是:
下圖來自Asynchronous and deferred JavaScript execution explained,清晰地解釋了普通狀況和這2種狀況下的區別
須要注意的是這兩個屬性目前對於內嵌的js是無效的
而對於dom中建立的script標籤在瀏覽器中則是異步的,以下所示:
var script = document.createElement('script'); script.src = 'a.js'; document.getElementsByTagName('head')[0].appendChild(script);
爲了解決js阻塞頁面的問題,能夠利用瀏覽器不認識的屬性來先下載js後再執行,如ControlJS就是這樣作的,它能提升頁面的相應速度,不過須要注意處理在js未加載完時的顯示效果
document.write是不推薦的api,對於標示有async或defer屬性的script標籤,使用它會致使不可預料的結果,除此以外還有如下場景是不該該使用它的:
簡單來講,document.write只適合用在外鏈的script標籤中,它最多見的場景是在廣告中,因爲廣告可能包含大量html,這時須要注意標籤的閉合,若是寫入的內容不少,爲了不受到頁面的影響,可使用相似Google AdSense的方式,經過建立iframe來放置廣告,這樣作還能減小廣告中的js執行對當前頁面性能的影響
另外,可使用ADsafe等方案來保證嵌入第三方廣告的安全,請參考如何安全地嵌入第三方js – FBML/caja/sandbox/ADsafe簡介
將script標籤放底部能夠提升頁面展示給用戶的速度,然而不少時候事情並沒那麼簡單,如頁面中的有些功能是依賴js的,因此更多的還須要根據實際需求進行調整
js壓縮可使用YUI Compressor或Closure Compiler
gwt中的js壓縮還針對gzip進行了優化,進一步減少傳輸的體積,具體請閱讀On Reducing the Size of Compressed Javascript
比起js放底部,css放頁面頂部就比較容易作到
使用@import在IE下會因爲css加載延後而致使頁面展示比使用link標籤慢,不過目前幾乎沒有人使用@import,因此問題不大,具體細節請參考don’t use @import
瀏覽器在構建DOM樹的過程當中會同時構建Render樹,咱們能夠簡單的認爲瀏覽器在遇到每個DOM節點時,都會遍歷全部selector來判斷這個節點會被哪些selector影響到
不過實際上瀏覽器通常是從右至左來判斷selector是否命中的,對於ID、Class、Tag、Universal和Page的規則是經過hashmap的方式來查找的,它們並不會遍歷全部selector,因此selector越精確越好,google page-speed中的一篇文檔Use efficient CSS selectors詳細說明了如何優化selector的寫法
另外一個比較好的方法是從架構層面進行優化,將頁面不一樣部分的模塊和樣式綁定,經過不一樣組合的方式來生成頁面,避免後續頁面頂部的css只增不減,愈來愈複雜和混亂的問題,能夠參考Facebook的靜態文件管理
如下整理一些性能優化相關的工具及方法
以前提到的http://www.browserscope.org收集了各類瀏覽器參數的對比,如最大連接數等信息,方便參考
Navigation Timing是還在草案中的獲取頁面性能數據api,能方便頁面進行性能優化的分析
傳統的頁面分析方法是經過javascript的時間來計算,沒法獲取頁面在網絡及渲染上所花的時間,使用Navigation Timing就能很好地解決這個問題,具體它能取到哪些數據能夠經過下圖瞭解(來自w3c)
目前這個api較新,目前只在一些比較新的瀏覽器上有支持,如Chrome、IE9,但也佔用必定的市場份額了,能夠如今就用起來
yahoo開源的一個頁面性能檢測工具,它的原理是經過監聽頁面的onbeforeunload事件,而後設置一個cookie,並在另外一個頁面中設置onload事件,若是cookie中有設置且和頁面的refer保持一致,則經過這兩個事件的事件來衡量當前頁面的加載時間
另外就是經過靜態圖片來衡量帶寬和網絡延遲,具體能夠看boomerang
from:http://blog.csdn.net/horkychen/article/details/8933370