1. 程式人生 > >聊一聊如何搭建高效能網站哪一些事

聊一聊如何搭建高效能網站哪一些事

在開發中經常會遇到網站的效能平靜下來,開啟慢的情況。我們平常開發中怎麼一步一步排查這些問題並 解決問題呢

在快節奏的時代中,慢是個不容忍受的事情。

一、 為什麼會‘慢’呢?

慢的情況是多種多樣的,比如:

  • 使用者體驗敢差,感覺“慢”
  • javascript執行慢。
  • 介面響應慢。
  • 資源載入慢。
  • 瀏覽器渲染慢。
  • 。。。

我們只能要求自己,所以使用者手機網速慢是不存在的~。

二、 排查手段

我們直接進入則會難過i天

我們將從幾個方面著手講一下排查問題的手段。

2.1 技術上的選擇

在前端日常開發中,技術上的選擇是非常重要的。為什麼要講這個呢?因為現象頻發。

前端工程化嚴重的當下,輕量化的框架慢慢被遺忘掉了。並不是所有的業務場景都適合使用工程化框架,react/vue 並不輕量。

複雜的框架是為了解決複雜的業務

如果研發h5宣發、PC展示等場景簡單的業務時候,javascript原生 配合一些輕量化外掛更適合。

多頁面應用也並不都是缺點。根據業務不同而選擇不一樣的技術是非常重要的,是每個前端都應該反思的事情。

這方面是導致卡頓的關鍵問題。

2.2 NetWrok

我們的老朋友NetWrok想必前端同學都很熟悉。我們先來看一下network面板

從面板上我們可以看出一些資訊:

  • 請求資源size
  • 請求資源時長
  • 請求資源數量
  • 介面響應時長
  • 介面發起數量
  • 介面報文size
  • 介面響應狀態
  • 瀑布圖

瀑布圖是什麼呢?

瀑布圖就是上方圖片後面的waterfall縱列

瀑布圖是一個級聯圖, 展示了瀏覽器如何載入資源並渲染成網頁. 圖中的每一行都是一次單獨的瀏覽器請求. 這個圖越長, 說明載入網頁過程中所發的請求越多. 每一行的寬度, 代表瀏覽器發出請求並下載該資源的過程中所耗費的時間。它的側重點在於分析網路鏈路

瀑布圖顏色說明:

  • DNS Lookup [深綠色] - 在瀏覽器和伺服器進行通訊之前, 必須經過DNS查詢, 將域名轉換成IP地址. 在這個階段, 你可以處理的東西很少. 但幸運的是, 並非所有的請求都需要經過這一階段.

  • Initial Connection [橙色] - 在瀏覽器傳送請求之前, 必須建立TCP連線. 這個過程僅僅發生在瀑布圖中的開頭幾行, 否則這就是個效能問題(後邊細說).

  • SSL/TLS Negotiation [紫色] - 如果你的頁面是通過SSL/TLS這類安全協議載入資源, 這段時間就是瀏覽器建立安全連線的過程. 目前Google將HTTPS作為其 搜尋排名因素 之一, SSL/TLS 協商的使用變得越來越普遍了.

  • Time To First Byte (TTFB) [綠色] - TTFB 是瀏覽器請求傳送到伺服器的時間+伺服器處理請求時間+響應報文的第一位元組到達瀏覽器的時間. 我們用這個指標來判斷你的web伺服器是否效能不夠, 或者說你是否需要使用CDN.

  • Downloading (藍色) - 這是瀏覽器用來下載資源所用的時間. 這段時間越長, 說明資源越大. 理想情況下, 你可以通過控制資源的大小來控制這段時間的長度.

那麼除了瀑布圖的長度外,我們如何才能判斷一個瀑布圖的狀態是健康的呢?

  • 首先, 減少所有資源的載入時間. 亦即減小瀑布圖的寬度. 瀑布圖越窄, 網站的訪問速度越快.

  • 其次, 減少請求數量 也就是降低瀑布圖的高度. 瀑布圖越矮越好.

  • 最後, 通過優化資源請求順序來加快渲染時間. 從圖上看, 就是將綠色的"開始渲染"線向左移. 這條線向左移動的越遠越好.

這樣,我們就可以從network的角度去排查“慢”的問題。

2.3 webpack-bundle-analyzer

專案構建後生成的bundle包是壓縮後的。webpack-bundle-analyzer是一款包分析工具。

我們先來看一下它能帶來的效果。如下圖:

從上圖來看,我們的bundle包被解析的一覽無餘。其中模組面積佔的越大說明在bundle包中size越大。就值得注意了,重點優化一下。

它能夠排查出來的資訊有

  • 顯示包中所有打入的模組
  • 顯示模組size 及 gzip後的size

排查包中的模組情形是非常有必要的,通過webpack-bundle-analyzer來排查出一些無用的模組,過大的模組。然後進行優化。以減少我們的bundle包size,減少載入時長。

安裝

# NPM 
npm install --save-dev webpack-bundle-analyzer
# Yarn 
yarn add -D webpack-bundle-analyzer

使用(as a Webpack-Plugin)

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

然後構建包完畢後會自動彈出一個視窗展示上圖資訊。

2.4 Performance

chrome自帶的performance模組。先附上一個官網文件傳送門:Performance

可以檢測很多方面的資料,多數情況的效能排查上用的比較多。如果想要深入瞭解的同學建議去看一下官方文件。

接下來我們來說一下在performance面板中如何排差“慢”的問題,它給我們提供了哪些資訊呢。先附上一張performance的面板圖片。

從上圖中可以分析出一些指標

  • FCP/LCP 時間是否過長?
  • 請求併發情況 是否併發頻繁?
  • 請求發起順序 請求發起順序是否不對?
  • javascript執行情況 javascript執行是否過慢?

這些指標就是我們需要重點關注的,當然performance的功能並不止於此。

先記住如何獲取到這些指標,後面來一一進行解析優化。

2.5 抓包

有一些業務狀況是沒有上述的一些除錯工具該怎麼辦呢?我們可以利用抓包工具進行對頁面資訊對抓取,上述我們通過chrome工具排查出來的指標,也可以通過抓包工具進行抓取。

這裡我推薦一款抓包工具charles。使用教程網上很多,自行搜尋即可

三、優化指標

這裡我們來講一下如何優化上述指標和一些導致慢的情況

3.1 tree shaking

中文(搖樹),webpack構建優化中重要一環。搖樹用於清除我們專案中的一些無用程式碼,它依賴於ES中的模組語法。

比如日常使用lodash的時候

import _ from 'lodash' 

如果如上引用lodash庫,在構建包的時候是會把整個lodash包打入到我們的bundle包中的。

import _isEmpty from 'lodash/isEmpty';

 

如果如上引用lodash庫,在構建包的時候只會把isEmpty這個方法抽離出來再打入到我們的bundle包中。

這樣的化就會大大減少我們包的size。所以在日常引用第三方庫的時候,需要注意匯入的方式。

如何開啟搖樹

在webpack4.x 中預設對tree-shaking進行了支援。 在webpack2.x 中使用tree-shaking:傳送門

3.2 split chunks

中文(分包)

在沒配置任何東西的情況下,webpack 4 就智慧的幫你做了程式碼分包。入口檔案依賴的檔案都被打包進了main.js,那些大於 30kb 的第三方包,如:echarts、xlsx、dropzone等都被單獨打包成了一個個獨立 bundle。

其它被我們設定了非同步載入的頁面或者元件變成了一個個chunk,也就是被打包成獨立的bundle。

它內建的程式碼分割策略是這樣的:

  • 新的 chunk 是否被共享或者是來自 node_modules 的模組
  • 新的 chunk 體積在壓縮之前是否大於 30kb
  • 按需載入 chunk 的併發請求數量小於等於 5 個
  • 頁面初始載入時的併發請求數量小於等於 3 個

大家可以根據自己的專案環境來更改配置。配置程式碼如下:

splitChunks({
  cacheGroups: {
    vendors: {
      name: `chunk-vendors`,
      test: /[\\/]node_modules[\\/]/,
      priority: -10,
      chunks: 'initial',
    },
    dll: {
      name: `chunk-dll`,
      test: /[\\/]bizcharts|[\\/]\@antv[\\/]data-set/,
      priority: 15,
      chunks: 'all',
      reuseExistingChunk: true
    },
    common: {
      name: `chunk-common`,
      minChunks: 2,
      priority: -20,
      chunks: 'all',
      reuseExistingChunk: true
    },
  }
})

 

沒有使用webpack4.x版本的專案,依然可以通過按需載入的形式進行分包,使得我們的包分散開,提升載入效能。

按需載入也是以前分包的重要手段之一

這裡推薦一篇非常好的文章:webpack如何使用按需載入

3.3 拆包

與3.2的分包不同。大家可能沒發現,上面2.3的bundle包解析中有個有趣的現象,上面專案的技術棧是react,但是bundle包中並沒有react、react-dom、react-router等。

因為把這些外掛“拆”開了。並沒有一起打在bundle中。而是放在了CDN上。下面我舉一個例子來解釋一下。

假設:原本bundle包為2M,一次請求拉取。拆分為 bundle(1M) + react桶(CDN)(1M) 兩次請求併發拉取。

從這個角度來看,1+1的模式拉取資源更快。

換一個角度來說,全量部署專案的情況,每次部署bundle包都將重新拉取。比較浪費資源。react桶的方式可以命中強快取,這樣的化,就算全量部署也只需要重新拉取左側1M的bundle包即可,節省了伺服器資源。優化了載入速度。

注意:在本地開發過程中,react等資源建議不要引入CDN,開發過程中重新整理頻繁,會增加CDN服務其壓力,走本地就好。

gzip

服務端配置gzip壓縮後可大大縮減資源大小。

Nginx配置方式

http {
  gzip on;
  gzip_buffers 32 4K;
  gzip_comp_level 6;
  gzip_min_length 100;
  gzip_types application/javascript text/css text/xml;
  gzip_disable "MSIE [1-6]\.";
  gzip_vary on;
}

配置完成後在response header中可以檢視。

3.5 圖片壓縮

開發中比較重要的一個環節,我司自己的圖床工具是自帶壓縮功能的,壓縮後直接上傳到CDN上。

如果公司沒有圖床工具,我們該如何壓縮圖片呢?我推薦幾種我常用的方式

  • 智圖壓縮 (百度很難搜到官網了,免費、批量、好用)
  • tinypng(免費、批量、速度塊)
  • fireworks工具壓縮畫素點和尺寸 (自己動手,掌握尺度)
  • 找UI壓縮後發給你

圖片壓縮是常用的手法,因為裝置畫素點的關係,UI給予的圖片一般都是 x2,x4的,所以壓縮就非常有必要。

3.6 圖片分割

如果頁面中有一張效果圖,比如真機渲染圖,UI手拿著刀不讓你壓縮。這時候不妨考慮一下分割圖片。

建議單張土圖片的大小不要超過100k,我們在分割完圖片後,通過佈局再拼接在一起。可以圖片載入效率。

這裡注意一點,分割後的每張圖片一定要給height,否則網速慢的情況下樣式會塌陷。

3.7 sprite

南方叫精靈圖,北方叫雪碧圖。這個現象就很有趣。

在網站中有很多小圖片的時候,一定要把這些小圖片合併為一張大的圖片,然後通過background分割到需要展示的圖片。

這樣的好處是什麼呢?先來普及一個規則

瀏覽器請求資源的時候,同源域名請求資源的時候有最大併發限制,chrome為6個,就比如你的頁面上有10個相同CDN域名小圖片,那麼需要發起10次請求去拉取,分兩次併發。第一次併發請求回來後,發起第二次併發。

如果你把10個小圖片合併為一張大圖片的畫,那麼只用一次請求即可拉取下來10個小圖片的資源。減少伺服器壓力,減少併發,減少請求次數。

附上一個sprite的例子。

3.8 CDN

中文(內容分發網路),伺服器是中心化的,CDN是“去中心化的”。

在專案中有很多東西都是放在CDN上的,比如:靜態檔案,音訊,視訊,js資源,圖片。那麼為什麼用CDN會讓資源載入變快呢?

舉個簡單的例子:

以前買火車票大家都只能去火車站買,後來我們買火車票就可以在樓下的火車票代售點買了。

你細品。

所以靜態資源度建議放在CDN上,可以加快資源載入的速度。

3.9 懶載入。

懶載入也叫延遲載入,指的是在長網頁中延遲載入影象,是一種非常好的優化網頁效能的方式。

當可視區域沒有滾到資源需要載入的地方時候,可視區域外的資源就不會載入。

可以減少伺服器負載,常適用於圖片很多,頁面較長的業務場景中。

如何使用懶載入呢?

  • 圖片懶載入
  • layzr.js

3.10 iconfont

中文(字型圖表),現在比較流行的一種用法。使用字型圖表有幾種好處

  • 向量
  • 輕量
  • 易修改
  • 不佔用圖片資源請求。

就像上面說的雪碧圖,如果都用字型圖示來替換的畫,一次請求都免了,可以直接打到bundle包中。

使用前提是UI給點力,設計趨向於字型圖示,提前給好資源,建立好字型圖示庫。

3.11 邏輯後移

邏輯後移是一種比較常見的優化手段。用一個開啟文章網站的操作來舉個例子。

沒有邏輯後移處理的請求順序是這個樣子的

頁面的展示主體是文章展示,如果文章展示的請求靠後了,那麼渲染文章出來的時間必然靠後,因為有可能因為請求阻塞等情況,影響請求響應情況,如果超過一次併發的情況的話,會更加的慢。如圖的這種情況也是在我們專案中發生過的。

很明顯我們應該把主體“請求文章”介面前移,把一些非主體的請求邏輯後移。這樣的話可以儘快的把主體渲染出來,就會快很多。

優化後的順序是這個樣子的。

在平常的開發中建議時常注意邏輯後移的情況,突出主體邏輯。可以極大的提升使用者體驗。

3.12 演算法複雜度

在資料量大的應用場景中,需要著重注意演算法複雜度問題。

在這個方面可以參考Javascript演算法之複雜度分析這篇文章。

如上面Performance解析出的Javascript執行指標上,可以推測出來你的code執行效率如何,如果執行時間過長就要考慮一下是否要優化一下複雜度了。

在時間換空間,空間換時間的選擇上,要根據業務場景來進行取捨。

3.13 元件渲染

拿react舉例,元件分割方面不要太深。需要控制組件的渲染,尤其是深層元件的render。

老生常談的話題,我們可以一些方式來優化元件渲染

  • 宣告週期控制 - 比如react的shouldComponentUpdate來控制組件渲染。
  • 官網提供的api- PureComponent
  • 控制注入元件的引數
  • 分配元件唯一key

沒有必要的渲染是對效能的極大浪費。

3.14 node middleware

中文(node 中介軟體)

中介軟體主要是指封裝所有Http請求細節處理的方法。一次Http請求通常包含很多工作,如記錄日誌、ip過濾、查詢字串、請求體解析、Cookie處理、許可權驗證、引數驗證、異常處理等,但對於Web應用而言,並不希望接觸到這麼多細節性的處理,因此引入中介軟體來簡化和隔離這些基礎設施與業務邏輯之間的細節,讓我們能夠關注在業務的開發上,以達到提升開發效率的目的。

使用node middleware合併請求。減少請求次數。這種方式也是非常實用的。

3.15 web worker

Web Worker 的作用,就是為 JavaScript 創造多執行緒環境,允許主執行緒建立 Worker 執行緒,將一些任務分配給後者執行。在主執行緒執行的同時,Worker 執行緒在後臺執行,兩者互不干擾。等到 Worker 執行緒完成計算任務,再把結果返回給主執行緒。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 執行緒負擔了,主執行緒(通常負責 UI 互動)就會很流暢,不會被阻塞或拖慢。

合理實用web worker可以優化複雜計算任務。這裡直接拋阮一峰的入門文章:傳送門

3.16 快取

快取方面可以講非常多的內容,我會單獨開一篇快取全攻略文章來詳細講述,大家可以➕個關注哈,關注越多寫的越快。

合理的利用強快取、快取協商、動態快取等等機制,可以極大提升使用者體驗及頁面開啟速度。在開發過程中,快取也成為了不可或缺的一部分。

四、END

上面整理了一些在實際業務開發中遇到的關於頁面載入慢的排查和解決的方法。後面還會越來月豐富起來,如果你的專案有可能遇到開啟慢的情況,不妨點贊收藏一下~。

END
為了更高的交流,歡迎大家關注我的公眾號,掃描下面二維碼即可關注,謝謝: