背景
做過前端開發都知道前端的工作內容是很多的,對於HTML、CSS、Javascript、Image、Flash等各種內容的使用。為了更好提升應用的性能,我們需要對各種資源內容進行不同方面的優化。
對用户而言,優化可以讓應用的響應速度加快,加載更加迅速,可以帶來更好的使用體驗。
對於服務商而言,前端優化能夠減少頁面請求數量,寬帶所佔帶寬,有效的節省資源。
前端優化的內容很多,按照粒度等級劃分可以大致分為兩類:頁面優化級別和代碼級別優化。
頁面優化主要針對頁面加載環節,包括:HTTP請求數、腳本的無阻塞加載、內聯腳本的位置優化等內容。代碼優化包括:Javascript中的DOM操作優化、CSS選擇符優化、圖片優化以及HTML結構優化等內容。
代碼級別優化則更關注數據請求,很重要的一條就是減少HTTP請求的數量。一個完整的HTTP請求需要經過路由查找,TCP握手,發送請求,服務器響應和瀏覽器接收等一些列過程。對於小文件,實際下載文件的時間對於整個請求的時間佔比很低,因此需要將小文件合併為大文件來傳輸。
(圖片來自網絡)
頁面級別:提升頁面加載速度
加載優化是為了解決頁面內容加載速度受限於網絡帶寬,過於耗時的問題,主要手段有:
項目打包優化
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中定義,而非<img>標籤。
使用雪碧圖有兩個明顯的優點:
- 降低網頁圖片內容對服務器的請求次數
雪碧圖可以合併大多數的背景圖片和小圖標,方便我們在任何位置使用。不同位置的請求只會調用同一個圖片,大大減少頁面對服務器的請求次數,降低服務器的壓力;這樣也可以提高頁面的加載速度,節約服務器的流量。 - 提升頁面加載速度
雪碧圖拼接的圖片尺寸明顯小於所有圖片拼合之前的打小。
從這兩方面可以明顯對前端請求速度進行優化。
在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壓縮的經典例題。
減少文件大小會帶來兩個明顯的好處:
- 減少存儲空間
- 通過網絡傳輸時可以減少傳輸時間
Gzip 壓縮背後的原理,是在一個文本文件中找出一些重複出現的字符串、臨時替換它們,從而使整個文件變小。也正是因為這個原理,文件中代碼的重複率越高,Gzip壓縮的效率就越高,使用 Gzip 的收益也就越大。反之亦然。
項目打包優化
(圖片來自網絡)
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中定義,而非<img>標籤。
使用雪碧圖有兩個明顯的優點:
- 降低網頁圖片內容對服務器的請求次數
雪碧圖可以合併大多數的背景圖片和小圖標,方便我們在任何位置使用。不同位置的請求只會調用同一個圖片,大大減少頁面對服務器的請求次數,降低服務器的壓力;這樣也可以提高頁面的加載速度,節約服務器的流量。 - 提升頁面加載速度
雪碧圖拼接的圖片尺寸明顯小於所有圖片拼合之前的打小。
從這兩方面可以明顯對前端請求速度進行優化。
在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壓縮的經典例題。
減少文件大小會帶來兩個明顯的好處:
- 減少存儲空間
- 通過網絡傳輸時可以減少傳輸時間
Gzip 壓縮背後的原理,是在一個文本文件中找出一些重複出現的字符串、臨時替換它們,從而使整個文件變小。也正是因為這個原理,文件中代碼的重複率越高,Gzip壓縮的效率就越高,使用 Gzip 的收益也就越大。反之亦然。
代碼級別:減少數據請求次數
前面我們列舉了在頁面初始加載時的優化方法,然而在某些場景下這還不夠,因為經常會出現頁面展示和使用時,頻繁請求服務來更新信息的場景。
例如在開發類Excel在線協同系統時,因為單元格業務相互獨立,全屏刷新無法滿足需求。我們只能定時從服務器獲取每個單元格的值,檢測到變化後展示在頁面上。而每個單元格分別調用api獲取內容,就會產生大量網絡請求。大量的請求一方面拖累了加載速度,頁面也會發生卡頓。
在這種場景下,WebSocket是一個很好的選擇,通過長鏈接的方式保持與服務器的同步,服務端主動推送更新到客户端,減少了網絡的開銷。但是WebSocket也有自身的缺點,開發成本高,無論是客户端還是服務端都需要考慮斷開重連、頻繁推送、資源佔用等問題。所以,我們還需要通過優化,儘量減少請求頻率。
優化思路
如何減少數據請求數量?我們可以通過請求隊列的方式,對邏輯進行優化。
(通過請求隊列優化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在線協同編輯的實例,詳細介紹了“數據請求隊列化”的實現,希望對您的前端開發有幫助。
- SpreadJS 的異步函數示例
問卷調查
2020年,一場突發的黑天鵝事件讓“在線辦公”站上了風口,釘釘、企業微信、飛書等線上辦公軟件不斷涌現,徹底顛覆了傳統的企業管理模式,大幅提高了企業的辦公效率。
2021年,“在線辦公”的熱度並未隨着全面復工復產而衰減,相反,在數字化浪潮下,在線辦公市場潛力巨大,賽道內的老玩家們都在持續加碼,而新玩家們也開始跑步入場。
對於開發者而言,如何才能研發出適應未來辦公場景的在線文檔系統,乘上“在線辦公”風口,破“BATHZ”陣局?
為了回答這一問題,葡萄城擬舉辦一場主題為“表格產品在協同文檔中的應用及未來發展”的研討會,現收集大家關於本場活動的想法:https://www.wenjuan.com/s/nmA...