從頁面載入到資料請求,前端頁面效能優化實踐分享

葡萄城技術團隊 發表於 2021-07-14
前端

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

背景

做過前端開發都知道前端的工作內容是很多的,對於HTML、CSS、Javascript、Image、Flash等各種內容的使用。為了更好提升應用的效能,我們需要對各種資源內容進行不同方面的優化。

對使用者而言,優化可以讓應用的響應速度加快,載入更加迅速,可以帶來更好的使用體驗。
對於服務商而言,前端優化能夠減少頁面請求數量,寬頻所佔頻寬,有效的節省資源。

前端優化的內容很多,按照粒度等級劃分可以大致分為兩類:頁面優化級別和程式碼級別優化。
頁面優化主要針對頁面載入環節,包括:HTTP請求數、指令碼的無阻塞載入、內聯指令碼的位置優化等內容。程式碼優化包括:Javascript中的DOM操作優化、CSS選擇符優化、圖片優化以及HTML結構優化等內容。

程式碼級別優化則更關注資料請求,很重要的一條就是減少HTTP請求的數量。一個完整的HTTP請求需要經過路由查詢,TCP握手,傳送請求,伺服器響應和瀏覽器接收等一些列過程。對於小檔案,實際下載檔案的時間對於整個請求的時間佔比很低,因此需要將小檔案合併為大檔案來傳輸。

1.webp

(圖片來自網路)

頁面級別:提升頁面載入速度

載入優化是為了解決頁面內容載入速度受限於網路頻寬,過於耗時的問題,主要手段有:
專案打包優化
Webpack 是一個前端資源載入/打包工具。它將根據模組的依賴關係進行靜態分析,然後將這些模組按照指定的規則生成對應的靜態資源。通常我們使用Webpack將多種靜態資源js、css、less 轉換成一個靜態檔案,減少了頁面的請求。
核心概念有:
Output:告訴 webpack 在哪裡輸出它所建立的 bundles,以及如何命名這些檔案,預設值為 ./dist。
Module:Webpack 會從配置的 Entry 開始遞迴找出所有依賴的模組。
Chunk:一個 Chunk 由多個模組組合而成,用於程式碼合併與分割。
Loader:loader 可以將所有型別的檔案轉換為 webpack 能夠處理的有效模組,然後你就可以利用 webpack 的打包能力,對它們進行處理。
Plugin:被用於轉換某些型別的模組,而外掛則可以用於執行範圍更廣的任務。

雪碧圖(CSS Sprite)
CSS雪碧 即CSS Sprite,也有人叫它CSS精靈,是一種CSS影像合併技術,該方法是將小圖示和背景影像合併到一張圖片上,然後利用css的背景定位來顯示需要顯示的圖片部分。

雪碧圖實現的基本原理是把我們從網上用到圖片整合在同一張圖片中,從而可以減少網站HTTP的請求數量。這一張圖片使用CSS background和background-position屬性渲染,
這意味著我們的標籤變得更加複雜,圖片是在CSS中定義,而非從頁面載入到資料請求,前端頁面效能優化實踐分享標籤。

使用雪碧圖有兩個明顯的優點:

  1. 降低網頁圖片內容對伺服器的請求次數
    雪碧圖可以合併大多數的背景圖片和小圖示,方便我們在任何位置使用。不同位置的請求只會呼叫同一個圖片,大大減少頁面對伺服器的請求次數,降低伺服器的壓力;這樣也可以提高頁面的載入速度,節約伺服器的流量。

  2. 提升頁面載入速度
    雪碧圖拼接的圖片尺寸明顯小於所有圖片拼合之前的打小。
    從這兩方面可以明顯對前端請求速度進行優化。
    在HTTP2之後,已經不需要考慮減少請求數,故雪碧圖現在在前端頁面優化效能的意義已經不大。現在更加推薦使用字型圖示,檔案很小並且是向量圖示
    CDN加速
    CDN的全稱是Content Delivery Network,即內容分發網路。其目的是通過在現有的Internet中增加一層新的CACHE(快取)層,將網站的內容釋出到最接近使用者的網路”邊緣“的節點,使使用者可以就近取得所需的內容,提高使用者訪問網站的響應速度。從技術上全面解決由於網路頻寬小、使用者訪問量大、網點分佈不均等原因,提高使用者訪問網站的響應速度。

Cache層技術可以用來消除峰值資料訪問造成的節點裝置阻塞。Cache伺服器具有快取功能,絕大部分的網頁物件的重複訪問不需要從原始網站重新傳送檔案,只需要通過簡單認證將副本傳送即可。快取伺服器的位置通常不輸在使用者端附近,所以可以獲得區域網的響應速度,有效減少廣域寬頻消耗。
對於提升響應速、節約頻寬、有效減輕源伺服器的負載十分有效。

總結來說CDN對網路的優化作用主要體現在如下幾個方面: 

  • 解決伺服器端的“第一公里”問題  
  • 緩解甚至消除了不同運營商之間互聯的瓶頸造成的影響  
  • 減輕了各省的出口頻寬壓力  
  • 緩解了骨幹網的壓力  
  • 優化了網上熱點內容的分佈

gzip壓縮
Gzip是GNUzip的縮寫,是一個GNU自由軟體的檔案壓縮程式,在使用中基本可以壓縮50%的文字檔案大小。在說Gzip之前,我們先介紹一個概念,HTTP 壓縮。HTTP 壓縮是一種內建到網頁和網頁客戶端中以改進傳輸速度和頻寬利用率的方式。在使用 HTTP 壓縮的情況下,HTTP 資料在從伺服器傳送前就已壓縮:相容的瀏覽器將在下載所需的格式前宣告支援何種方法給伺服器;不支援壓縮方法的瀏覽器將下載未經壓縮的資料。
HTTP 壓縮就是以縮小體積為目的,對 HTTP 內容進行重新編碼的過程。
Gzip就是HTTP壓縮的經典例題。

減少檔案大小會帶來兩個明顯的好處:

  1. 減少儲存空間
  2. 通過網路傳輸時可以減少傳輸時間

Gzip 壓縮背後的原理,是在一個文字檔案中找出一些重複出現的字串、臨時替換它們,從而使整個檔案變小。也正是因為這個原理,檔案中程式碼的重複率越高,Gzip壓縮的效率就越高,使用 Gzip 的收益也就越大。反之亦然。

專案打包優化

2.jpg

(圖片來自網路)

Webpack 是一個前端資源載入/打包工具。它將根據模組的依賴關係進行靜態分析,然後將這些模組按照指定的規則生成對應的靜態資源。通常我們使用Webpack將多種靜態資源js、css、less 轉換成一個靜態檔案,減少了頁面的請求。
核心概念有:
Output:告訴 webpack 在哪裡輸出它所建立的 bundles,以及如何命名這些檔案,預設值為 ./dist。
Module:Webpack 會從配置的 Entry 開始遞迴找出所有依賴的模組。
Chunk:一個 Chunk 由多個模組組合而成,用於程式碼合併與分割。
Loader:loader 可以將所有型別的檔案轉換為 webpack 能夠處理的有效模組,然後你就可以利用 webpack 的打包能力,對它們進行處理。
Plugin:被用於轉換某些型別的模組,而外掛則可以用於執行範圍更廣的任務。

雪碧圖(CSS Sprite)

3.webp

(圖片來自網路)

CSS雪碧 即CSS Sprite,也有人叫它CSS精靈,是一種CSS影像合併技術,該方法是將小圖示和背景影像合併到一張圖片上,然後利用css的背景定位來顯示需要顯示的圖片部分。

雪碧圖實現的基本原理是把我們從網上用到圖片整合在同一張圖片中,從而可以減少網站HTTP的請求數量。這一張圖片使用CSS background和background-position屬性渲染,
這意味著我們的標籤變得更加複雜,圖片是在CSS中定義,而非從頁面載入到資料請求,前端頁面效能優化實踐分享標籤。

使用雪碧圖有兩個明顯的優點:

  1. 降低網頁圖片內容對伺服器的請求次數
    雪碧圖可以合併大多數的背景圖片和小圖示,方便我們在任何位置使用。不同位置的請求只會呼叫同一個圖片,大大減少頁面對伺服器的請求次數,降低伺服器的壓力;這樣也可以提高頁面的載入速度,節約伺服器的流量。
  2. 提升頁面載入速度
    雪碧圖拼接的圖片尺寸明顯小於所有圖片拼合之前的打小。
    從這兩方面可以明顯對前端請求速度進行優化。
    在HTTP2之後,已經不需要考慮減少請求數,故雪碧圖現在在前端頁面優化效能的意義已經不大。現在更加推薦使用字型圖示,檔案很小並且是向量圖示

CDN加速

4.jpg

(圖片來自網路)

CDN的全稱是Content Delivery Network,即內容分發網路。其目的是通過在現有的Internet中增加一層新的CACHE(快取)層,將網站的內容釋出到最接近使用者的網路”邊緣“的節點,使使用者可以就近取得所需的內容,提高使用者訪問網站的響應速度。從技術上全面解決由於網路頻寬小、使用者訪問量大、網點分佈不均等原因,提高使用者訪問網站的響應速度。

Cache層技術可以用來消除峰值資料訪問造成的節點裝置阻塞。Cache伺服器具有快取功能,絕大部分的網頁物件的重複訪問不需要從原始網站重新傳送檔案,只需要通過簡單認證將副本傳送即可。快取伺服器的位置通常不輸在使用者端附近,所以可以獲得區域網的響應速度,有效減少廣域寬頻消耗。
對於提升響應速、節約頻寬、有效減輕源伺服器的負載十分有效。

總結來說CDN對網路的優化作用主要體現在如下幾個方面: 

  • 解決伺服器端的“第一公里”問題  
  • 緩解甚至消除了不同運營商之間互聯的瓶頸造成的影響  
  • 減輕了各省的出口頻寬壓力  
  • 緩解了骨幹網的壓力  
  • 優化了網上熱點內容的分佈

gzip壓縮

5.jpg

(圖片來自網路)

Gzip是GNUzip的縮寫,是一個GNU自由軟體的檔案壓縮程式,在使用中基本可以壓縮50%的文字檔案大小。在說Gzip之前,我們先介紹一個概念,HTTP 壓縮。HTTP 壓縮是一種內建到網頁和網頁客戶端中以改進傳輸速度和頻寬利用率的方式。在使用 HTTP 壓縮的情況下,HTTP 資料在從伺服器傳送前就已壓縮:相容的瀏覽器將在下載所需的格式前宣告支援何種方法給伺服器;不支援壓縮方法的瀏覽器將下載未經壓縮的資料。
HTTP 壓縮就是以縮小體積為目的,對 HTTP 內容進行重新編碼的過程。
Gzip就是HTTP壓縮的經典例題。

減少檔案大小會帶來兩個明顯的好處:

  1. 減少儲存空間

  2. 通過網路傳輸時可以減少傳輸時間

Gzip 壓縮背後的原理,是在一個文字檔案中找出一些重複出現的字串、臨時替換它們,從而使整個檔案變小。也正是因為這個原理,檔案中程式碼的重複率越高,Gzip壓縮的效率就越高,使用 Gzip 的收益也就越大。反之亦然。

程式碼級別:減少資料請求次數

前面我們列舉了在頁面初始載入時的優化方法,然而在某些場景下這還不夠,因為經常會出現頁面展示和使用時,頻繁請求服務來更新資訊的場景。

例如在開發類Excel線上協同系統時,因為單元格業務相互獨立,全屏重新整理無法滿足需求。我們只能定時從伺服器獲取每個單元格的值,檢測到變化後展示在頁面上。而每個單元格分別呼叫api獲取內容,就會產生大量網路請求。大量的請求一方面拖累了載入速度,頁面也會發生卡頓。

6.gif

在這種場景下,WebSocket是一個很好的選擇,通過長連結的方式保持與伺服器的同步,服務端主動推送更新到客戶端,減少了網路的開銷。但是WebSocket也有自身的缺點,開發成本高,無論是客戶端還是服務端都需要考慮斷開重連、頻繁推送、資源佔用等問題。所以,我們還需要通過優化,儘量減少請求頻率。

優化思路

如何減少資料請求數量?我們可以通過請求佇列的方式,對邏輯進行優化。

7.png

(通過請求佇列優化Web請求)

經過優化,類Excel線上協同系統獲取資料的邏輯變成了如下的樣子:

  • 當單元格傳送請求時,請求先新增ID,並通過ID快取callback方法,然後進入請求佇列,佇列管理器定時或者根據佇列中請求數量多少像服務端傳送請求包。
  • 服務端接收到請求包後批量處理,處理後封裝新的返回包
  • 前端接受到返回包後根據請求的唯一ID,呼叫對應的callback方法執行,完成單元格的請求
    使用此方法進行優化,優點是顯而易見的:
  • 實現簡單,程式碼改動小,原本的ajax請求改為佇列呼叫即可,請求後的callbak無需修改。服務端新增一個新介面拆分請求即可。
  • 根據實際場景設定請求頻率或者一次請求中資料的數量,兼顧更新頻率和相應次數。

應用例項

下面程式碼是GETNUMBERFROMSERVER的實現,該函式負責呼叫伺服器的getData介面,傳遞引數,獲取顯示內容並展示在單元格。為了確保非同步更新單元格的使用者體驗,這個函式源自SpreadJS的非同步函式。

1.	    var GetNumberFromServer = function () {
2.	    };
3.	    GetNumberFromServer.prototype = new GC.Spread.CalcEngine.Functions.AsyncFunction("GETNUMBERFROMSERVER", 1, 2);
4.	    GetNumberFromServer.prototype.evaluate = function (context, arg1, arg2) {
5.	    fetch("/spread/getData?data="+arg1)
6.	    .then(function(response) {
7.	        return response.text();
8.	    })
9.	    .then(function(text) {
10.	        context.setAsyncResult(text);
11.	    });
12.	    };
13.	    GC.Spread.CalcEngine.Functions.defineGlobalCustomFunction("GETNUMBERFROMSERVER", new GetNumberFromServer());
14.	

為了減少請求,我們首先需要使用一個快取物件存放請求資料,定時呼叫介面處理。

1.	let callStack = {}; //收集請求資料
2.	let callingStack = {}; //快取正在請求中的資料資訊
3.	let callStackCount = 0; //請求數量,當作請求ID,用於區分請求內容
4.	let timingId = 0; //用於判斷當前是否有定時器等待請求中


然後,我們定義新的佇列化請求方法,代替在函式中直接呼叫API介面。

1.	
2.	// data 請求資料
3.	// context 非同步函式context, 網路請求結束後回撥時使用
4.	// callback 回撥函式
5.	function stackCall(data, context, callback){
6.	    let id = callStackCount++;
7.	    callStack[id] = {};
8.	    callStack[id].data = data;
9.	    callStack[id].context = context;
10.	    callStack[id].callback = callback;
11.	
12.	    if(timingId === 0){ // 同時只有一個定時器
13.	        timingId = setTimeout(function(){
14.	            callingStack = callStack;
15.	            callStack = {};
16.	
17.	            let newData = "" //合併請求資料,根據實際業務情況整理
18.	            for(let cId in callingStack){
19.	                newData += (cId + "," + callingStack[cId].data + ";");
20.	            }
21.	
22.	            // 傳送請求,這裡模擬資料,傳送什麼返回什麼
23.	            fetch("/spread/getData?data=" + newData)
24.	                  .then(function(response) {
25.	                    return response.text();
26.	                  })
27.	                  .then(function(text) {
28.	                    let resData = newData.split(";");
29.	                    let spread = designer.getWorkbook();
30.	                    spread.suspendPaint(); //暫定頁面繪製
31.	
32.	                    //解析返回的資料
33.	                    for(let resId in resData){
34.	                        if(resData[resId]){
35.	                            let ress = resData[resId].split(",");
36.	                            // 根據Id,獲取函式的context,呼叫callback回撥
37.	                            callingStack[ress[0]].callback.call(null, callingStack[ress[0]].context, ress[1])
38.	                        }
39.	                    }
40.	                    spread.resumePaint(); //重啟統一繪製
41.	                timingId = 0;
42.	            });
43.	        }, 1000)
44.	    }
45.	}


最後更新非同步函式的實現方式,在函式中呼叫stackCall堆疊函式,批量呼叫成功後執行callback回撥中的setAsyncResult方法,最終實現業務邏輯。

1.	GetNumberFromServer.prototype.evaluate = function (context, arg1, arg2) {
2.	    stackCall(arg1, context, function(context, text){
3.	        context.setAsyncResult(text);
4.	    })
5.	    };



經過這次優化,當頁面有大量非同步請求時,這些請求會放到佇列中,定時統一處理,一次重新整理。

此外,我們還可以使用SpreadJS的doNotRecalculateAfterLoad匯入選項,在首次載入時不計算,改用json中原始值;以及calcOnDemand開啟按需計算。進一步優化頁面初始化的速度和體驗。


1.	json.calcOnDemand = true;
2.	spread.fromJSON(json, { doNotRecalculateAfterLoad: true });

總結

本文分類介紹了幾種前端效能優化的方法。這些最佳實踐覆蓋了頁面載入和資料請求環節。在文章的後半部分,我們通過類Excel線上協同編輯的例項,詳細介紹了“資料請求佇列化”的實現,希望對您的前端開發有幫助。