前端性能優化:網站性能優化

網站性能優化可以從下面總結點入手。

1. 減少HTTP請求

  • 使用雪碧圖 – CSS Sprites,把多個圖片合併到一個單獨的圖片中,利用CSS –background-position調整圖片顯示位置。這種方式適用面比較廣泛。
    缺點是,如果一張小圖,需要N個顏色,就必須做N個不同顏色的小圖,合併到大圖裏面。
  • 使用data:URL展示圖片,它可以在頁面中渲染圖片但無需額外的HTTP請求,請求格式:

<img scr="data:image/jpg;base64, xxxxxxxxxxxxxxxx">

 

缺點是:此方案不適合mobile應用;IE7以下不支持;如果一張圖片在多個頁面被用到,無法利用瀏覽器緩存。
為了解決無法緩存問題,可以將data:image應用到CSS樣式中,比如:

.imageA {
 background-image: url(data:image/jpg;base64, xxxxxxxxxxxxxxxx);
}

 

  • 合併腳本和樣式表
  • Multipart XHR
    運行客戶端用一個HTTP請求就可以從服務端傳遞多個資源。它通過在服務端將資源(CSS文件,HTML片段,Javascript代碼或者base64編碼的圖片)打包成一個由雙方約定的字符串分割的長字符串,並發送到客戶端。
    然後用Javascript代碼處理這個長字符串,並根據他的mime-type類型和傳入的其他『頭信息』解析出每個資源。
    例如,解析一串圖片編碼,輸入為req.responseText

function splitImages(imageString){
         var imageData = imageString.split('\u0001');
         var imageElement;

         for (var i =0, len = imageData.length; i<len; i++){
           imageElement = document.createElement('img');
           imageElement.src = 'data:image/jpeg;base64,' + imageData[i];
           document.getElementById('container').appendChild(imageElement);
         }
}

 

2. 使用CDN

內容發佈網絡(CDN)是一組分佈在多個不同地理位置的WEB服務器,用於更加有效地向用戶發佈內容。
CDN用於發佈靜態內容,如圖片,腳本,樣式表和Flash。

不使用CDN時:

  1. 用戶在瀏覽器訪問欄中輸入要訪問的域名。
  2. 瀏覽器向DNS服務器請求對該域名的解析。
  3. DNS服務器返回該域名的IP地址給瀏覽器。
  4. 瀏覽器使用該IP地址向服務器請求內容。
  5. 服務器將用戶請求的內容返回給瀏覽器。

盡量將CDN的域名設置的不同於請求方網站的域名。比如,網站為a.com,CDN域名可以設置為acdn.com。為什麼呢?

  • Cookie隔離:Cookie 是緊跟域名的,同一個域名下的所有請求,都會攜帶 Cookie。試想,海量請求圖片或JS/CSS文件時,還要攜帶Cookie,也會成為不小的開銷。

  • 並且,瀏覽器在同一個時刻向同一個域名請求文件的並行下載數量是有限的(Chrome為6個並發),所以,可以利用多個域名主機存放不同的靜態資源,增大頁面加載時資源並行下載數量。

3. 利用HTTP緩存

具體內容參考文章 

//www.cnblogs.com/yizhiamumu/p/16687989.html

4. 壓縮組件

開啟HTTP Gzip壓縮。

request: Accept-Encoding: gzip, deflate
response:Content-Encoding:gzip

 

5. 將樣式表放在頂部

外部腳本文件和CSS文件是並行下載的,把樣式表在頁面中的位置並不影響下載時間,但會影響頁面的呈現!瀏覽器必須要等樣式表加載完畢之後才渲染頁面
因此,應該把樣式表放在head中,這樣它就能被最先下載使頁面逐步呈現。

6. 將JS腳本放在底部

一般,JS腳本是被禁止並行下載的,因為JS腳本可能使用document.write來修改頁面內容,所以必須保證JS執行順序。
腳本下載後,必須執行完,才可以繼續後面的解析。

但是,Chrome瀏覽器支持並發下載資源文件,並保證按順序執行(參考《WebKit技術內幕-朱永盛》)。

7. 避免CSS表達式

CSS表達式是動態設置CSS屬性的一種強大(並且危險)的方式。CSS表達式求值頻率比人們期望的要高,它們不只在頁面呈現和大小變化時求知,甚至用戶鼠標在頁面上拖拽都要求知。
如,將背景色設置為每小時變化一次:

background-color:expression((new Date()).getHours()%2?"#ccc":"#000");

 

觸發頻率太高!不建議使用。

8. 使用外部JS和CSS

純粹來講,內聯的JS和CSS可以產生比外部文件文件更快的響應速度。
但是現實中,外部鏈接的JS和CSS文件會產生較快的頁面,是因為JS和CSS文件有可能被緩存

9. 減少DNS查找

DNS也是開銷。通常瀏覽器查找一個給定主機名的IP地址要花費20~120毫秒。在DNS查找完成之前,瀏覽器不能從主機名哪裡下載任何東西。
只要cline-server之間保持TCP連接打開狀態,就無需DNS查找。所以,我們可以通過使用Keep-Alive和較少的域名來減少DNS查找。

Keep-Alive,HTTP1.1協議中推出的持久連接。特點為:只要任意一端沒有明確提出斷開連接,則保持TCP連接狀態。

含有Keep-Alive首部的response示例:

HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Thu, 11 Aug 2016 15:23:13 GMT
Keep-Alive: timeout=5, max=1000
Last-Modified: Mon, 25 Jul 2016 04:32:39 GMT
Server: Apache

 

10. 壓縮JavaScript和CSS

可以用各類構建或者編譯工具壓縮腳本和樣式文件,比如:gulpwebpack

11. 少用iframe

iframe是開銷最高的DOM元素,它的缺點遠大於優點。

  • 不利於SEO:搜索引擎的檢索程序無法解讀iframe中的src
  • 阻塞onload事件:iframe不加載完畢,就不會觸發父窗口的onload事件。
  • 影響頁面資源並行加載:iframe和主頁面共享連接池,而瀏覽器對相同域的連接有限制,所以會影響頁面資源的並行加載。
    為了解決兩個問題,可以動態設置iframe中的src屬性,代碼如下:

<iframe id="iframe1" src=""></iframe>

<script>
document.getElementById('iframe1').src = "www.api.a.com";
</script>

 

12.少用Table

table內容渲染是將table的DOM渲染樹全部生成完並一次繪製到頁面上,所以,在渲染長表格時很耗性能,應該盡量避免使用。

可以使用uldiv替代。

13. JS文件異步/按需加載

有多種方式支持JavaScript異步加載。

  • Script DOM Element
    這恐怕是最常見的異步加載腳本方法,即,動態創建一個script標籤,並設置其src值。如下:

function createScript(url){
  var scrElem = document.createElement('script');
  srcElem.src = url;
  document.getElementsByTagName('head')[0].appendChild(scrElem);
}

 

優點:支持跨域加載腳本文件;兼容性最好、普適性最高的方案
缺點:腳本無序執行;會阻塞onload事件

  • XMLHttpRequest

通過XMLHttpRequest的方式下載腳本文件,然後使用eval或者動態添加<script>標籤並設置其text屬性來執行腳本。

// 不考慮IE
var xhrObj = new XMLHttpRequest();
xhrObj .onreadystatechange = function(){
  if (xhrObj .readyState == 4) {
    // 方式一
    eval(xhrObj.responseText);  
    // 方式二
    var scrElem = document.createElement('script');
    srcElem.text= xhrObj.responseText;
    document.getElementsByTagName('head')[0].appendChild(scrElem);
  }
}
xhrObj .open('GET', 'a.js', true);
xhrObj .send('');

 

優點:將腳本下載和腳本執行分離開,可以在適當的時候再執行腳本;不會阻塞onload事件
缺點;通過XMLHttpRequest獲取的腳本文件必須和主頁面是同一個域名下。也就是說,不支持跨域下載腳本(除非做跨域處理)。因此不適合加載第三方文件;腳本無序執行。

  • defer和async

兩者都支持異步加載文件,不同之處是,defer會在全部資源下載完畢後才執行JS文件;async在腳本文件下載完就立刻執行,並且,async模式加載的JS文件無法依序執行,對於有順序依賴的腳本來說,不應該採用這種方式。
defer相對友好一些,並可以保證JS文件按照順序執行

<script src="a.js" defer></script>
<script src="a.js" async></script>

 

defer和async優點:支持跨域加載腳本文件。
defer優點:可以保證JS文件按照順序執行。

defer和async缺點:IE10以上(包括IE10)才支持。
async缺點:JS文件無法依序執行;會阻塞onload事件

14. 圖片懶加載

通過圖片懶加載可以讓一些不可視的圖片不去加載,避免一次性加載過多的圖片導致請求阻塞(瀏覽器一般對同一域名下的並發請求的連接數有限制),這樣就可以提高網站的加載速度,提高用戶體驗。

實現方案一

第一步: 懶加載的img標籤的src設置縮略圖或者不設置src,然後自定義一個屬性,值為真正的圖片或者原圖的地址(比如data-src)。

// //a.com/logo.png 是圖片的真實地址,設置到data-src屬性上。
<img data-src="//a.com/logo.png" class="lazy-image"/> 
// css部分 
.lazy-image { 
    background: url('loading.gif') no-repeat center; 
} 

 

第二步:頁面加載完後,獲取所有需要懶加載的圖片的元素集合,判斷是否在可視區域,如果是在可視區域的話,設置元素的src屬性值為真正圖片的地址。

// 監聽滾動事件
document.addEventListener('scroll', inViewShow);
// 顯示圖片
inViewShow() {     
    let imageElements = Array.prototype.slice.call(document.querySelectorAll('.lazy-image'))    
    let len = imageElements.length     
    for(let i = 0; i < len; i++) {         
        let imageElement = imageElements[i]        
        const rect = imageElement.getBoundingClientRect() // 出現在視野的時候加載圖片         
        if(rect.top < document.documentElement.clientHeight) {             
            imageElement.src = imageElement.dataset.src // 賦值到真正的src上           
            imageElements.splice(i, 1)             
            len--             
            i--         
        }     
    } 
}

 

實現方案二

利用瀏覽器新特性IntersectionObserver

IntersectionObserver接口提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態的方法)。

// 渲染
public render() {
    const { src } = this.state
    // 一旦src變化,更新為img標籤,加載圖片
    if (src) {
      return <img {...this.props} src={src} />
    }
   // 佔位圖片
    return (
      <span ref={(ele) => (this.ele = ele)}  />
    )
  }

// 監控
public componentDidMount() {
    // 判斷瀏覽器是否支持IntersectionObserver函數
    if (typeof IntersectionObserver === 'function') {
      const cb = (entries) => {
        if (entries.some((item) => item.intersectionRatio > 0)) {
          const { src } = this.props
          this.setState({ src })
          this.ob.disconnect() // 取消監聽
        }
      }
      this.ob = new IntersectionObserver(cb, {
        root: document.body, // 祖先元素
        threshold: [0, 0.01], // 交叉值
      })
      this.ob.observe(this.ele)
    } else {
      const { src } = this.props
      this.setState({ src })
    }
 }

 

實現方案三

使用三方庫處理圖片懶加載。

15. 避免頁面中空的href和src

link標籤中的href,或者iframscriptimg標籤的src屬性為空時,瀏覽器在渲染過程中仍然會將hrefsrc中的空內容進行加載,直到失敗為止。這樣會阻塞頁面中其他資源的下載過程。

16. 減少頁面重定向

頁面重定向會延長頁面內容返回的等待時間,一次重定向大致需要600毫秒。

17. 使用HTTP2.0

相比HTTP1.1,2.0版本有了更強大的能力,可以提升傳輸性能。

  • TCP多路復用能力:這和HTTP1.1的連接復用還是不同的。HTTP1.1的連接復用(keep-alive)為應用層,指HTTP復用TCP連接後,可以串行發送多個HTTP請求。而HTTP2.0多路復用在傳輸層,幀的多路復用,即不同文件的傳輸幀可以在一個TCP連接中同時傳輸,也就是多個HTTP請求並行發送。
  • 採用二進制格式傳輸數據,非HTTP 1.x的文本格式(默認)。且採用HPACK壓縮傳輸,最大限度節省了寬帶。
  • 支持傳輸流的優先級和流量控制:服務端可以優先輸出CSS文件,而不必依賴HTML中定義的腳本加載順序。
 
image.png

18. 預加載dns-prefetchpreconnectprefetchprerender

dns-prefetch:使瀏覽器主動去執行域名解析。

<link rel="dns-prefetch" href="//fonts.google.com/" >

 

preconnect:提前建立連接。比dns-prefetch多走了兩步,除了完成dns解析之外,還完成了TCP握手(HTTPS下還完成TLS握手)。

<link rel="preconnect" href="//fonts.google.com/" >

  

prefetch:讓瀏覽器預加載一個資源(HTML,JS,CSS或者圖片等),可以讓用戶跳轉到其他頁面時,響應速度更快。

<link rel="prefetch" href="//css-tricks.com/a.png">

  

prerender:不僅會加載資源,還會解執行頁面,進行預渲染(太重,不建議使用)

<link rel="prefetch" href="//css-tricks.com/a.html">