前端性能優化

原文鏈接:https://maimai.cn/article/detail?fid=1327048438&efid=7hjZiacjzcOEc-T5aedSUQ

前端的性能優化方向

從傳輸層面去優化的方向

預解析地址 首次請求解析地址如果沒有緩存 那麼可能消耗60-120ms

preload預請求必要內容,prefetch預請求可能需要內容

這種請求方式不會阻塞瀏覽器的解析,而且能將預請求的資源緩存起來,而且可以設置crossorgin進行跨域資源的緩存,不會推遲首屏的渲染時間,還會加快後面的加載時間,因爲後面的本身需要的資源會直接從緩存中讀取,而不會走網絡請求。

使用 preload 前,在遇到資源依賴時進行加載:

使用 preload 後,不管資源是否使用都將提前加載:

 

可以看到,preload 的資源加載順序將被提前:

使用 preload 後,Chrome 會有一個警告:


preload 和 prefetch 混用的話,並不會複用資源,而是會重複加載。
若不確定資源是必定會加載的,則不要錯誤使用 preload,以免本末倒置,給頁面帶來更沉重的負擔。

preload 加載頁面必需的資源如 CDN 上的字體文件,與 prefetch 預測加載下一屏數據,興許是個不錯的組合。

 

減少傳輸次數

部分圖片base64處理,然後使用雪碧圖。多張圖拼成一張傳輸

當然base64這個東西慎用,實際開發中它表現並那麼好

減少傳輸體積

例如後端返回數據:“該用戶沒有擁有權限”

可以改成:0

約定優於配置的思想一定要有

使用probbuffer協議

ProtoBuffer是由谷歌研發的對象序列化和反序列化的開源工具

它的本質就是將一段數據序列化,轉變成二進制形式傳輸

然後另外的服務器端或者客戶端接受到之後 反序列化,轉換成對應的數據格式(json

好像還有人沒有聽說這個傳輸協議 其實它傳輸過程就是2進制流的形式

用得最多的是和GRPC配合Go語言或者服務器之間傳輸數據

例如IM應用,每個IM應用都是一個服務端 也是一個客戶端

那麼對於這種頻繁傳輸數據的時候,可以使用protobuffer傳輸協議

protobuffer下載

protobuffer有幾個優點:

1.平臺無關,語言無關,可擴展;
2.提供了友好的動態庫,使用簡單;
3.解析速度快,比對應的XML快約20-100倍;
4.序列化數據非常簡潔、緊湊,與XML相比,其序列化之後的數據量約爲1/3到1/10。

protobuffer.js - 我們可以使用這個庫來解析

protobuf.js 提供了幾種方式來處理proto

直接解析,如protobuf.load("awesome.proto", function(err, root) {...})

轉化爲JSONjs後使用,如protobuf.load("awesome.json", function(err, root) {...})

當然我們一般轉換成.js後使用

代碼層次優化:

封裝數據對象

可以用對象進行大數據封裝,儘量用對象key-value形式封裝

如果需要對象遍歷 其實也有很多種方法可以做到

用對象有個好處 就是數據量大起來但是需要查找的時候會非常快

避免書寫耗時的同步代碼

不管前端怎麼發展,js主線程是單線程,並且與GUI渲染線程互斥還是沒有變

爲什麼?

因爲js可以進行dom操作 爲了防止在渲染過程出現dom操作而造成不可預見後果

現代框架的底層其實還是dom操作 並且直接的dom操作比數據驅動要快多!

例如:

  •  
  •  
  •  
  •  
  •  
for(let i=0; i< 100000; i++){    console.log(i)}
console.log(1)

for循環其實很快,但是走完這100000次循環的耗時,到打印出1,有可能超過100ms

那麼如果這個打印輸出1是一個用戶交互操作 就會讓用戶有了延遲卡頓的現象

所謂的卡,並不是電腦或者手機帶不動我們的代碼,而是js線程和GUI渲染互斥造成的假象。(大部分是這情況,也有配置特別低的)

如果非要同步代碼的場景?

那麼我建議ES6的異步方案,或者改變實現方案,因爲大部分性能方案優化是卡在這個點。

手機端白屏,持久化存儲等解決網絡傳輸慢等方案

淘寶等task-slice 方案

淘寶task-slice方案

先不說這篇文章實現最終效果怎樣,但是這種思想在前端裏是可以大量使用的,Go語言裏就有切片

渲染任務分割後打開性能調試面板

可以看到是一點點渲染出來的 也算是加快了首屏渲染吧!

切片隊列的核心代碼:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
function* sliceQueue({ sliceList, callback }) {    let listOrNum = (isNum(sliceList) && sliceList) || (isArray(sliceList) && sliceList.length);    for (let i = 0; i < listOrNum; ++i) {        const start = performance.now();        callback(i);        while (performance.now() - start < 16.7) {            yield;        }    }}

跟我的React框架編寫的每幀清空渲染隊列有點類似:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
/** * 隊列   先進先出 後進後出 ~ * @param {Array:Object} setStateQueue  抽象隊列 每個元素都是一個key-value對象 key:對應的stateChange value:對應的組件 * @param {Array:Component} renderQueue  抽象需要更新的組件隊列 每個元素都是Component */const setStateQueue = [];const renderQueue = [];
function defer(fn) {  //requestIdleCallback的兼容性不好,對於用戶交互頻繁多次合併更新來說,requestAnimation更有及時性高優先級,requestIdleCallback則適合處理可以延遲渲染的任務~  //   if (window.requestIdleCallback) {  //     console.log('requestIdleCallback');  //     return requestIdleCallback(fn);  //   }  //高優先級任務  return requestAnimationFrame(fn);}export function enqueueSetState(stateChange, component) {  if (setStateQueue.length === 0) {    //清空隊列的辦法是異步執行,下面都是同步執行的一些計算    defer(flush);  }...//dosomething
}

數據持久化存儲

PWA,漸進式web應用

將數據資源儲存在緩存中,每次請求前判斷是否在Service Worker中,如果沒有再請求網絡資源

PWA 的主要特點包括下面三點:

可靠 - 即使在不穩定的網絡環境下,也能瞬間加載並展現
體驗 - 快速響應,並且有平滑的動畫響應用戶的操作
粘性 - 像設備上的原生應用,具有沉浸式的用戶體驗,用戶可以添加到桌面

當用戶打開我們站點時(從桌面 icon 或者從瀏覽器),通過 Service Worker 能夠讓用戶在網絡條件很差的情況下也能瞬間加載並且展現。

Service Worker 是用 JavaScript 編寫的 JS 文件,能夠代理請求,並且能夠操作瀏覽器緩存,通過將緩存的內容直接返回,讓請求能夠瞬間完成。開發者可以預存儲關鍵文件,可以淘汰過期的文件等等,給用戶提供可靠的體驗。

如果站點加載時間超過 3s,53% 的用戶會放棄等待。頁面展現之後,用戶期望有平滑的體驗,過渡動畫和快速響應。

爲了保證首屏的加載,我們需要從設計上考慮,在內容請求完成之前,可以優先保證 App Shell 的渲染,做到和 Native App 一樣的體驗,App Shell 是 PWA 界面展現所需的最小資源。

文檔寫得最好的,還是百度的lavas

Service Worker生命週期分爲這麼幾個狀態 安裝中, 安裝後, 激活中, 激活後, 廢棄

 

安裝( installing ):這個狀態發生在 Service Worker 註冊之後,表示開始安裝,觸發 install 事件回調指定一些靜態資源進行離線緩存。

安裝後( installed ):Service Worker 已經完成了安裝,並且等待其他的 Service Worker 線程被關閉。

激活( activating ):在這個狀態下沒有被其他的 Service Worker 控制的客戶端,允許當前的 worker 完成安裝,並且清除了其他的 worker 以及關聯緩存的舊緩存資源,等待新的 Service Worker 線程被激活。

激活後( activated ):在這個狀態會處理 activate 事件回調 (提供了更新緩存策略的機會)。並可以處理功能性的事件 fetch (請求)、sync (後臺同步)、push (推送)。

廢棄狀態 ( redundant ):這個狀態表示一個 Service Worker 的生命週期結束。

Service Worker本質,可以看成另外一個線程啓動,做爲一箇中間件在發揮作用。

緩存的資源都是可以在這裏看到

Service Worker只能在localhost調試中或者http中使用,因爲它的權限過於強大,可以攔截請求等。所以要確保安全,目前PWA並不成熟,瀏覽器兼容性還是不那麼好,但是它用起來是真的很舒服

另外一種持久化存儲方案:

localStorage

瀏覽器APIlocalStorage.getItem等...

有類似將js文件緩存寫入localStorage 然後通過與服務端對比版本號再決定是否更新js文件

還有在進入首頁時,將詳情頁的模版先存入localStorage 當進入詳情頁時候直接取出,然後發請求,把請求回來的一小部分內容(比如圖片,渲染上去)

當然,還有種種的應用,騷操作。

最後是框架,現在的單頁面框架,其實很簡單。每次更新頁面,diff對比差異後,更新差異部分。

精細化拆分組件 , 經常變和不經常變的分拆

精細化定製數據來源,最好做到單向數據流,只有一個數據改變可以影響重新渲染

並不是所有的都需要在shouldComponentUpdate中對比然後決定是否要更新

實踐證明 複用1000個組件渲染在頁面中

immutable去生成不可變數據對比

跟用PureComponent淺比較 後者會快很多很多

永遠別忘了js主線程和GUI渲染線程互斥。

合理手段減少重複渲染次數

如何優化你的超大型React應用
前端性能優化不完全手冊 - 很早前寫的文章

發現性能優化其實要寫的太多太多,但是,核心點在上面和文章裏了,特別是我的那個清空渲染隊列的代碼,我決定能解決很大部分的性能瓶頸。

負載均衡,Nginxpm2配置

在理解Nginx的用途之前先了解正向代理、反向代理的概念:

正向代理:是一個位於客戶端和原始服務器(origin server)之間的服務器,爲了從原始服務器取得內容,客戶端向代理髮送一個請求並指定目標(原始服務器),然後代理向原始服務器轉交請求並將獲得的內容返回給客戶端。

反向代理:在計算機網絡中,反向代理是代理服務器的一種。它根據客戶端的請求,從後端的服務器上獲取資源,然後再將這些資源返回給客戶端。與正向代理不同,正向代理作爲一個媒介將互聯網上獲取的資源返回給相關聯的客戶端,而反向代理是在服務器端作爲代理使用,而不是客戶端。

PM2是一款非常好用的Node.js服務啓動容器。它可以讓你保持應用程序永遠運行,要重新加載它們無需停機(我是這麼理解的:PM2是一個監控工具)。

nginx是一款輕量化的web服務器。相較於Apache具有佔有內存少,併發高等優勢。使用epoll模型,nginx的效率很高。並且可以熱升級。

Nginx與PM2的區別:

pm2是在應用層面單機的負載,nginx是多用於多機集羣的負載PM2 Cluster 是對單臺服務器而言的,而 nginx 是對多臺服務器而言的,它們可以很好的結合在一起。全篇看下來會發現,其實Nginx與PM2完全是不一樣的,兩者之間沒有很大的相同點讓人混淆。換一種更容易理解的說法是:nginx配置多站點(域名),pm2管理nodejs後臺進程

使用PM2永動機啓動Node.js項目,再使用nginx做反向代理,簡直完美。

因爲node.js程序監聽的是服務器端口,使用nginx做反向代理,就可以任意配置你的二級域名來訪問你的程序

這裏我們主要介紹nginx的負載模塊

HTTP負載均衡模塊(HTTP Upstream)

這個模塊爲後端的服務器提供簡單的負載均衡(輪詢(round-robin)和連接IP(client IP))

如下例:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
upstream backend  {  server backend1.example.com weight=5;  server backend2.example.com:8080;  server unix:/tmp/backend3;} server {  location / {    proxy_pass  http://backend;  }}

Nginx的負載均衡算法:

1.round robin(默認)
輪詢方式,依次將請求分配到各個後臺服務器中,默認的負載均衡方式。 
適用於後臺機器性能一致的情況。 
掛掉的機器可以自動從服務列表中剔除。

2.weight
根據權重來分發請求到不同的機器中,指定輪詢機率,weight和訪問比率成正比,用於後端服務器性能不均的情況。

例如:

  •  
  •  
  •  
  •  
upstream bakend {    server 192.168.0.14 weight=10;    server 192.168.0.15 weight=10;    }

3.IP_hash
根據請求者ip的hash值將請求發送到後臺服務器中,可以保證來自同一ip的請求被打到固定的機器上,可以解決session問題。

例如:

  •  
  •  
  •  
  •  
  •  
upstream bakend {    ip_hash;    server 192.168.0.14:88;    server 192.168.0.15:80;    }   

4.fair

根據後臺響應時間來分發請求,響應時間短的分發的請求多。

例如:

upstream backend { 
server server1; 
server server2; 
fair; 
}

5.url_hash
根據請求的urlhash值將請求分到不同的機器中,當後臺服務器爲緩存的時候效率高。

例如:

在upstream中加入hash語句,server語句中不能寫入weight等其他的參數,hash_method是使用的hash算法

  •  
  •  
  •  
  •  
  •  
  •  
upstream backend {server squid1:3128;server squid2:3128;hash $request_uri;hash_method crc32;}
常見的負載均衡算法使用pm2:

 

  •  
  •  
npm install pm2 -gpm2 start app.js

PM2 的主要特性

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
內建負載均衡(使用 Node cluster 集羣模塊)後臺運行0 秒停機重載,我理解大概意思是維護升級的時候不需要停機.具有 Ubuntu 和 CentOS 的啓動腳本停止不穩定的進程(避免無限循環)控制檯檢測提供 HTTP API遠程控制和實時的接口 API ( Nodejs 模塊,允許和 PM2 進程管理器交互 )

pm2常用命令

pm2的使用,讓我們避開了自己配置負載均衡,守護進程等一系列。但是高併發場景,Nginx和內置的負載均衡,僅僅只講到了皮毛,這裏只是入個門。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章