前言: css
在一樣的網絡環境下,兩個一樣能知足你的需求的網站,一個「Duang」的一下就加載出來了,一個糾結了半天才出來,你會選擇哪一個?研究代表:用戶最滿意的打開網頁時間是2-5秒,若是等待超過10秒,99%的用戶會關閉這個網頁。也許這樣講,各位還不會有太多感觸,接下來我列舉一組數據:Google網站訪問速度每慢400ms就致使用戶搜索請 求降低0.59%;Amazon每增長100ms網站延遲將致使收入降低1%;雅虎若是有400ms延遲會致使流量降低5-9%。網站的加載速度嚴重影響了用戶體驗,也決定了這個網站的生死存亡。html
可能有人會說:網站的性能是後端工程師的事情,與前端並沒有多大關係。我只能說,too young too simple。事實上,只有10%~20%的最終用戶響應時間是用在從Web服務器獲取HTML文檔並傳送到瀏覽器的,那剩餘的時間去哪兒了?來瞄一下性能黃金法則:前端
只有10%~20%的最終用戶響應時間花在了下載HTML文檔上。其他的80%~90%時間花在了下載頁面中的全部組件上。linux
接下來咱們將研究一下前端攻城獅如何來提升頁面的加載速度。web
1、儘可能減小 HTTP 請求後端
有幾種常見的方法能切實減小 HTTP 請求:瀏覽器
一、 合併腳本跟樣式文件,如能夠把多個 CSS 文件合成一個,把多個 JS 文件合成一個。緩存
二、 CSS Sprites 利用 CSS background 相關元素進行背景圖絕對定位,把多個圖片合成一個圖片。服務器
三、字體圖標,如iconfont.cn,font awesome網絡
2、使用CDN
CDN(內容發佈網絡)是一組分佈在多個不一樣地理位置的Web服務器,用於更加有效地向用戶發佈內容。在優化性能時,向特定用戶發佈內容的服務器的選擇基於對網絡慕課擁堵的測量。例如,CDN可能選擇網絡階躍數最小的服務器,或者具備最短響應時間的服務器。
CDN還能夠進行數據備份、擴展存儲能力,進行緩存,同時有助於緩和Web流量峯值壓力。
CDN的缺點:
一、響應時間可能會受到其餘網站流量的影響。CDN服務提供商在其全部客戶之間共享Web服務器組。
二、若是CDN服務質量降低了,那麼你的工做質量也將降低
三、沒法直接控制組件服務器
3、添加Expires頭
頁面的初次訪問者會進行不少HTTP請求,可是經過使用一個長久的Expires頭,可使這些組件被緩存,下次訪問的時候,就能夠減小沒必要要的HTPP請求,從而提升加載速度。
Web服務器經過Expires頭告訴客戶端可使用一個組件的當前副本,直到指定的時間爲止。例如:
Expires: Fri, 18 Mar 2016 07:41:53 GMT
Expires缺點: 它要求服務器和客戶端時鐘嚴格同步;過時日期須要常常檢查
HTTP1.1中引入Cache-Control來克服Expires頭的限制,使用max-age指定組件被緩存多久。
Cache-Control: max-age=12345600
若同時制定Cache-Control和Expires,則max-age將覆蓋Expires頭
4、壓縮組件
從HTTP1.1開始,Web客戶端能夠經過HTTP請求中的Accept-Encoding頭來表示對壓縮的支持
Accept-Encoding: gzip,deflate
若是Web服務器看到請求中有這個頭,就會使用客戶端列出來的方法中的一種來進行壓縮。Web服務器經過響應中的Content-Encoding來通知 Web客戶端。
Content-Encoding: gzip
5、將樣式表放在頭部
將樣式表放在文檔底部會阻止瀏覽器中的內容逐步出現。爲了不當樣式變化時重繪頁面元素,瀏覽器會阻塞內容逐步呈現,形成「白屏」。這源自瀏覽器的行爲:若是樣式表仍在加載,構建呈現樹就是一種浪費,由於全部樣式表加載解析完畢以前務虛會之任何東西
6、將腳本放在底部
更樣式表相同,腳本放在底部對於實際頁面加載的時間並不能形成太大影響,可是這會減小頁面首屏出現的時間,使頁面內容逐步呈現。
js的下載和執行會阻塞Dom樹的構建(嚴謹地說是中斷了Dom樹的更新),因此script標籤放在首屏範圍內的HTML代碼段裏會截斷首屏的內容。
下載腳本時並行下載是被禁用的——即便使用了不一樣的主機名,也不會啓用其餘的下載。由於腳本可能修改頁面內容,所以瀏覽器會等待;另外,也是爲了保證腳本可以按照正確的順序執行,由於後面的腳本可能與前面的腳本存在依賴關係,不按照順序執行可能會產生錯誤。
7、避免CSS表達式
CSS表達式是動態設置CSS屬性的一種強大而且危險的方式,它受到了IE5以及以後版本、IE8以前版本的支持。
8、使用外部的JavaScript和CSS
內聯腳本或者樣式能夠減小HTTP請求,按理來講能夠提升頁面加載的速度。然而在實際狀況中,當腳本或者樣式是從外部引入的文件,瀏覽器就有可能緩存它們,從而在之後加載的時候可以直接使用緩存,而HTML文檔的大小減少,從而提升加載速度。
9、減小DNS查找
當咱們在瀏覽器的地址欄輸入網址(譬如: www.linux178.com) ,而後回車,回車這一瞬間到看到頁面到底發生了什麼呢?
域名解析 --> 發起TCP的3次握手 --> 創建TCP鏈接後發起http請求 --> 服務器響應http請求,瀏覽器獲得html代碼 --> 瀏覽器解析html代碼,並請求html代碼中的資源(如js、css、圖片等) --> 瀏覽器對頁面進行渲染呈現給用戶
10、精簡JavaScript/css代碼
精簡就是從代碼中移除沒必要要的字符以減小文件大小,下降加載的時間。代碼精簡的時候會移除沒必要要的空白字符(空格,換行、製表符),這樣整個文件的大小就變小了。
11、避免重定向
重定向用於將用戶從一個URL從新路由到另外一個URL。
經常使用重定向的類型
301:永久重定向,主要用於當網站的域名發生變動以後,告訴搜索引擎域名已經變動了,應該把舊域名的的數據和連接數轉移到新域名下,從而不會讓網站的排名因域名變動而受到影響。
302:臨時重定向,主要實現post請求後告知瀏覽器轉移到新的URL。
304:Not Modified,主要用於當瀏覽器在其緩存中保留了組件的一個副本,同時組件已通過期了,這是瀏覽器就會生成一個條件GET請求,若是服務器的組件並無修改過,則會返回304狀態碼,同時不攜帶主體,告知瀏覽器能夠重用這個副本,減小響應大小。
12、刪除重複腳本
在團隊開發一個項目時,因爲不一樣開發者之間均可能會向頁面中添加頁面或組件,所以可能相同的腳本會被添加屢次。
重複的腳本會形成沒必要要的HTTP請求(若是沒有緩存該腳本的話),而且執行多餘的JavaScript浪費時間,還有可能形成錯誤。
如何避免重複腳本呢?
1. 造成良好的腳本組織。重複腳本有可能出如今不一樣的腳本包含同一段腳本的狀況,有些是必要的,但有些卻不是必要的,因此須要對腳本進行一個良好的組織。
2. 實現腳本管理器模塊。
十3、配置ETag
實體標籤(EntityTag)是惟一標識了一個組件的一個特定版本的字符串,是web服務器用於確認緩存組件的有效性的一種機制,一般可使用組件的某些屬性來構造它。
十4、使Ajax可緩存
POST的請求,是不能夠在客戶端緩存的,每次請求都須要發送給服務器進行處理,每次都會返回狀態碼200。(能夠在服務器端對數據進行緩存,以便提升處理速度)
GET的請求,是能夠(並且默認)在客戶端進行緩存的,除非指定了不一樣的地址,不然同一個地址的AJAX請求,不會重複在服務器執行,而是返回304。
訪問DOM元素是有代價的,修改DOM元素則更爲昂貴,由於它會致使瀏覽器從新計算頁面的幾何變化。
最壞的狀況是在循環中訪問修改元素,尤爲是對HTML元素集合循環操做。
十6、最小化 iframe 的數量
在寫網頁的時候,咱們可能會用到iframe,iframe的好處是它徹底獨立於父文檔。iframe中包含的JavaScript文件訪問其父文檔是受限的。例如,來自不一樣域的iframe不能訪問其父文檔的Cookie。
即便iframe是空的,其開銷也會很高,並且他會阻塞onload事件。因此,咱們應該儘量避免iframe的使用。
十7、杜絕 http 404 錯誤
十8、CSS選擇器優化
一、在談論選擇器優化以前,咱們先簡單介紹一下選擇器的類型:
ID選擇器 : #id;
類選擇器: .class
標籤選擇器: a
兄弟選擇器:#id + a
子選擇器: #id > a
後代選擇器: #id a
通賠選擇器: *
屬性選擇器: input[type='input']
僞類和僞元素:a:hover , div:after
組合選擇器:#id,.class
二、瀏覽器的匹配規則
#abc > a怎麼匹配? 有人可能會覺得:先找到id爲abc的元素,再查找子元素爲a的元素!!too young,too simple!
其實,瀏覽器時從右向左匹配選擇符的!!!那麼上面的寫法效率就低了:先查找頁面中的全部a標籤,在看它的父元素是否是id爲abc
知道了瀏覽器的匹配規則咱們就能儘量的避免開銷很大的選擇器了:
避免通配規則
除了 * 以外,還包括子選擇器、後臺選擇器等。
而它們之間的組合更加逆天,譬如:li *
瀏覽器會查找頁面的全部元素,而後一層一層地尋找他的祖先,看是否是li,這對可能極大地損耗性能。
不限定ID選擇器
ID就是惟一的,不要寫成相似div#nav這樣,不必。
不限定class選擇器
咱們能夠進一步細化類名,譬如li.nav 寫成 nav-item
儘可能避免後代選擇器
一般後代選擇器是開銷最高的,若是能夠,請使用子選擇器代替。
替換子選擇器
若是能夠,用類選擇器代替子選擇器,譬如
nav > li 改爲 .nav-item
依靠繼承
瞭解那些屬性能夠依靠繼承得來,從而避免重複設定規則。
三、關鍵選擇符
選擇器中最右邊的選擇符成爲關鍵選擇符,它對瀏覽器執行的工做量起主要影響。
舉個栗子:
div div li span.class-special
乍一看,各類後代選擇器組合,性能確定不能忍。其實仔細一想,瀏覽器從右向左匹配,若是頁面中span.class-special的元素只有一個的話,那影響並不大啊。
反過來看,若是是這樣
span.class-special li div div ,儘管span.class-special不多,可是瀏覽器從右邊匹配,查找頁面中全部div在層層向上查找,那性能天然就低了。
四、重繪與迴流
優化css選擇器不只僅提升頁面加載時候的效率,在頁面迴流、重繪的時候也能夠獲得不錯的效果,那麼接下來咱們說一下重繪與迴流。
4.一、從瀏覽器的渲染過程談起
解析HTML構建dom樹→構建render樹→佈局render樹→繪製render樹
1)構建dom樹
根據得到的html代碼生成一個DOM樹,每一個節點表明一個HTML標籤,根節點是document對象。dom樹種包含了全部的HTML標籤,包括未顯示的標籤(display:none)和js添加的標籤。
2)構建cssom樹
將獲得全部樣式(瀏覽器和用戶定義的css)除去不能識別的(錯誤的以及css hack),構建成一個cssom樹
3)cssom和dom結合生成渲染樹,渲染樹中不包括隱藏的節點包括(display:none、head標籤),並且每一個節點都有本身的style屬性,渲染樹種每個節點成爲一個盒子(box)。注意:透明度爲100%的元素以及visibility:hidden的元素也包含在渲染樹之中,由於他們會影響佈局。
4)瀏覽器根據渲染樹來繪製頁面
4.二、重繪(repaint)與迴流(reflow)
1)重繪 當渲染樹中的一部分或者所有由於頁面中某些元素的佈局、顯示與隱藏、尺寸等改變須要從新構建,這就是迴流。每一個頁面至少會發生一次迴流,在頁面第一次加載的時候發生。在迴流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並從新構造這部分渲染樹,完成迴流後,瀏覽器會從新繪製受影響的部分到屏幕中,該過程成爲重繪。
2. 當渲染樹中的一些元素須要更新屬性,而這些屬性不會影響佈局,隻影響元素的外觀、風格,好比color、background-color,則稱爲重繪。
注意:迴流必將引發重繪,而重繪不必定會引發迴流。
4.三、迴流什麼時候發生:
當頁面佈局和幾何屬性改變時就須要迴流。下述狀況會發生瀏覽器迴流:
一、添加或者刪除可見的DOM元素;
二、元素位置改變;
三、元素尺寸改變——邊距、填充、邊框、寬度和高度
四、內容改變——好比文本改變或者圖片大小改變而引發的計算值寬度和高度改變;
五、頁面渲染初始化;
六、瀏覽器窗口尺寸改變——resize事件發生時;
4.四、如何影響性能
頁面上任何一個結點觸發reflow,都會致使它的子結點及祖先結點從新渲染。
每次重繪和迴流發生時,瀏覽器會根據對應的css從新繪製須要渲染的部分,若是你的選擇器不優化,就會致使效率下降,因此優化選擇器的重要性可見一斑。