前端項目(Vue/React)性能優(yōu)化
前言
前端隨著node等JavaScript運行時平臺的出現(xiàn),逐漸向工程化方向發(fā)展。項目開發(fā)也越來越規(guī)范化,但是隨著項目的體積越來越大,依賴庫越來越多,項目的運行,熱更新和打包發(fā)布也是越來越慢,甚至卡頓。這個時候就需要對項目進行“瘦身”(性能優(yōu)化)了。本文就圍繞著如何給前端項目進行性能優(yōu)化等技術(shù)點一一展開討論
為什么
為什么要進行項目性能優(yōu)化,其實這個問題我在前言中已經(jīng)簡單闡述過了。優(yōu)化的目的是為了改善用戶的使用體驗,提高用戶的留存率,你的產(chǎn)品頁面和功能的響應(yīng)的速度越快,交互更加的人性化,對用戶更加的友好,那自然而然的就會收到用戶的青睞啦。所以對項目的優(yōu)化不僅僅是要從技術(shù)思維去作為出發(fā)點,同時也要從產(chǎn)品思維出發(fā)站在用戶的角度(也就是一個使用者的角度)作為出發(fā)點。這樣的優(yōu)化才是有效優(yōu)化,否則就是東施效顰了,亂搞一通,隨大流。。。
Web 性能
這里聲明一下,本文只闡述web項目的性能優(yōu)化。其他平臺的項目是否適用,自行斟酌!
在對web項目優(yōu)化之前先了解一下web的性能指標(biāo),這里引用MDN中的一段描述。
Web 性能是客觀的衡量標(biāo)準(zhǔn),是用戶對加載時間和運行時的直觀體驗。Web 性能指頁面加載到可交互和可響應(yīng)所消耗的時間,以及頁面在交互時的流暢度——滾動是否順滑?按鈕能否點擊?彈窗能否快速打開,動畫是否平滑?Web 性能既包括客觀的度量如加載時間,每秒幀數(shù)和到頁面可交互的時間;也包括用戶的對頁面內(nèi)容加載時間的主觀感覺。
頁面響應(yīng)時間越長,越多的用戶就會放棄該網(wǎng)站。重要的是,通過使體驗盡可能早地變得可用和交互,同時異步地加載長尾體驗部分,來最大程度地減少加載和響應(yīng)時間,并添加其他功能以降低延遲。
Web性能指標(biāo)模型
RAIL 是 Response、Animation、Idle 和 Load 的首字母縮寫,是一種由 Google Chrome 團隊于 2015 年提出的性能模型,用于提升瀏覽器內(nèi)的用戶體驗和性能。

Response(響應(yīng)):在50ms內(nèi)處理事件
目標(biāo):在 100 ms內(nèi)完成由用戶輸入發(fā)起的轉(zhuǎn)換,讓用戶感覺交互是即時的。

Animatio(動畫): 在10ms內(nèi)生成一幀,目的為流暢的視覺效果
在 10 毫秒或更短的時間內(nèi)生成動畫的每一幀。從技術(shù)上來講,每幀的最大預(yù)算為 16 ms(1000 ms/每秒 60 幀≈16 ms),但是,瀏覽器需要大約 6 ms速來渲染一幀,因此,準(zhǔn)則為每幀 10ms。Idle(空閑):最大限度增加空閑時間
最大限度增加空閑時間以提高頁面在 50 ms內(nèi)響應(yīng)用戶輸入的幾率Load(加載):在5s內(nèi)交付并實現(xiàn)可交互
目前對于首次加載,在使用速度較慢 3G 連接的中端移動設(shè)備上,理想的目標(biāo)是在5s或更短的事件內(nèi)實現(xiàn)交互對于后續(xù)加載,理想的目標(biāo)是在2s內(nèi)加載頁面。
優(yōu)化方向
所以綜上所述,所以我們優(yōu)化的項主要是集中在:
http的請求的響應(yīng)
動畫的視覺和流暢效果
交互的響應(yīng)速度
頁面加載的時間
這四個大的方向
當(dāng)然除了這四個方向以為我覺得還可以有其他的途徑去進一步的優(yōu)化,當(dāng)然了,這肯定也是要看應(yīng)用場景的,根據(jù)業(yè)務(wù)的需要去具體問題具體分析的,不能夠為了優(yōu)化而去優(yōu)化。
也可以換個說法:
傳輸資源的優(yōu)化:比如圖像資源,不同的格式類型會有不同的使用場景,在使用過程中判斷是否恰當(dāng);
加載過程的優(yōu)化:比如加載延遲,是否有不需要在首屏展示的非關(guān)鍵信息,占用了頁面的加載時間;
JavaScript的優(yōu)化:JavaScript代碼是否進行了壓縮,書寫是否規(guī)范,有無考慮內(nèi)存泄漏等;
關(guān)鍵渲染路徑優(yōu)化:比如是否存在不必要的回流與重繪等;
本地存儲和瀏覽器緩存。
舉個栗子??,從資源請求數(shù)量+代碼執(zhí)行效率兩個角度來考慮,可以從DMO結(jié)構(gòu),JS腳本,webpack打包,服務(wù)端優(yōu)化,ssr,框架(Vue,React)的優(yōu)化等等
怎么做?
怎么做?當(dāng)然是從四個大的方向先入手啦,然后在根據(jù)你的業(yè)務(wù)和場景,再細(xì)分。
http的請求的響應(yīng)
優(yōu)化方案:
并行處理請求和響應(yīng)
減少服務(wù)器響應(yīng)時間
部分資源可以使用懶加載或者預(yù)加載
消除阻塞渲染的資源
避免過大的網(wǎng)絡(luò)負(fù)載,壓縮傳輸?shù)馁Y源
最小化關(guān)鍵請求的深度
使用緩存策略
減少重定向
使用CDN內(nèi)容分發(fā)網(wǎng)絡(luò)
根據(jù)需要使用SSR(服務(wù)端渲染)
Chrome限制每個域名最多執(zhí)行6個TCP連接。如果您一次請求十二個資源,前6個將開始,后6個將排隊。一旦其中一個請求完成,隊列中的第一個請求項目將開始其請求過程。
瀏覽器發(fā)起一個http請求的過程
Queuing (排隊)排隊時間
Stalled (停滯)發(fā)送請求之前等待的時間
DNS lookup (DNS查找),
initial connection (初始連接)
SSL handshake (SSL握手)
Request sent (請求發(fā)送)發(fā)出網(wǎng)絡(luò)請求所花費的時間
Waiting (等待)(到開始下載第一個字節(jié)的時間(TTFB))等待初始響應(yīng)所花費的時間
Content Download (內(nèi)容下載)接收響應(yīng)數(shù)據(jù)所花費的時間
動畫的視覺和流暢效果
前端前端實現(xiàn)動畫有三種主流的方式:csss,canvas,dom,他們在瀏覽器中的渲染方式有所不同,所以優(yōu)化的時候也要注意區(qū)分
進行CSS的動畫優(yōu)化必須了解一定的瀏覽器的幾個概念,圖層、重繪、回流。
css3動畫的優(yōu)化方案:
縮小CSS,移除未使用的CSS
避免DOM過大
減少重繪和回流
盡量將動畫放在一個圖層,避免多層污染
盡量使用GPU加速
Canvas動畫優(yōu)化方案
CSS雖然更加簡單也更加保證性能的下限,但是要想實現(xiàn)更加復(fù)雜可控的動畫,那就必須用到Canvas+JavaScript這個組合了.
Canvas作為瀏覽器提供的2D圖形繪制API本身有一定的復(fù)雜度,優(yōu)化的方法非常多,我們僅僅介紹幾種比較主流的優(yōu)化方式.
運用requestAnimationFrame
離屏canvas
避免浮點運算
減少調(diào)用Canvas API
使用web worker
交互的響應(yīng)速度
優(yōu)化方案:
減少第三方代碼的影響
減少Java Script執(zhí)行時間
最小化線程工作
保持較低的請求數(shù)和傳輸大小
使用節(jié)流和防抖減少事件的觸發(fā)頻率
頁面加載的時間
優(yōu)化方案:
縮小javascript
預(yù)連接到所需的來源
預(yù)先價值關(guān)鍵請求
減少對DOM的操作
減少http請求
圖片懶加載
優(yōu)化TCP協(xié)議
優(yōu)化css
異步加載腳本,防止主線程阻塞
使用cdn
代理緩存
下面是一些關(guān)于前端框架項目的一些優(yōu)化方法
Vue項目優(yōu)化
代碼層面的優(yōu)化
路由懶加載
{ path: '/', ? name: 'home', ? ? component: () => import(/* webpackChunkName: "home" */ './views/home/index.vue'), ? ? meta: { isShowHead: true } ? }
computed 和 watch 區(qū)分使用場景
computed: 是計算屬性,依賴其它屬性值,并且 computed 的值有緩存,只有它依賴的屬性值發(fā)生改變,下一次獲取 computed 的值時才會重新計算 computed 的值。當(dāng)我們需要進行數(shù)值計算,并且依賴于其它數(shù)據(jù)時,應(yīng)該使用 computed,因為可以利用 computed 的緩存特性,避免每次獲取值時,都要重新計算;
watch:類似于某些數(shù)據(jù)的監(jiān)聽回調(diào) ,每當(dāng)監(jiān)聽的數(shù)據(jù)變化時都會執(zhí)行回調(diào)進行后續(xù)操作;當(dāng)我們需要在數(shù)據(jù)變化時執(zhí)行異步或開銷較大的操作時,應(yīng)該使用 watch,使用 watch 選項允許我們執(zhí)行異步操作 ( 訪問一個 API ),限制我們執(zhí)行該操作的頻率,并在我們得到最終結(jié)果前,設(shè)置中間狀態(tài)。這些都是計算屬性無法做到的。
v-if 和 v-show 區(qū)分使用場景
v-if 適用于在運行時很少改變條件,不需要頻繁切換條件的場景;v-show則適用于需要非常頻繁切換條件的場景。這里要說的優(yōu)化點在于減少頁面中 dom 總數(shù),我比較傾向于使用 v-if,因為減少了 dom 數(shù)量。
v-for 遍歷必須為 item 添加 key,且避免同時使用 v-if
v-for 遍歷必須為 item 添加 key,循環(huán)調(diào)用子組件時添加 key,key 可以唯一標(biāo)識一個循環(huán)個體,可以使用例如 item.id 作為 key
避免同時使用 v-if,v-for 比 v-if 優(yōu)先級高,如果每一次都需要遍歷整個數(shù)組,將會影響速度。
vue-lazyload可參考下官方介紹,不再贅述。
style方面
style文件按照模塊劃分,無論放在內(nèi)外都<style scoped> 鎖住樣式,目的就是避免多人開發(fā)樣式混亂,鎖住之后內(nèi)部的命名也可以很簡短。
全局樣式抽象化,將公共組件以及elementUI修改的樣式建議都放到公共樣式,抽象做的越好說明你的樣式文件體積越小,復(fù)用率越高。
合理組件化
使用重復(fù)率高的模塊盡量封裝成組件,包括布局的封裝,按鈕,表單,提示框,彈出框等,封裝的組件只處理
類似業(yè)務(wù),復(fù)用率越高越好封裝組件配置的 props 細(xì)化到一個字段,不要一個對象傳進去,這樣只傳需要修改的參數(shù),在子組件 props 里加數(shù)據(jù)類型,是否必傳,以及默認(rèn)值,便于排查錯誤,讓傳值更嚴(yán)謹(jǐn)
Vue組件動態(tài)加載
Vue庫dist里面的Runtime-only比Runtime+Compiler小30%
Vue的計算屬性會根據(jù)依賴的data進行緩存
keep-alive可以緩存常用組件
Vuex中的getter也會根據(jù)依賴的state進行緩存
Vue全局錯誤處理errorHandle
長列表性能優(yōu)化
Vue 會通過 Object.defineProperty 對數(shù)據(jù)進行劫持,來實現(xiàn)視圖響應(yīng)數(shù)據(jù)的變化,然而有些時候我們的組件就是純粹的數(shù)據(jù)展示,不會有任何改變,我們就不需要 Vue 來劫持我們的數(shù)據(jù),在大量數(shù)據(jù)展示的情況下,這能夠很明顯的減少組件初始化的時間,那如何禁止 Vue 劫持我們的數(shù)據(jù)呢?可以通過 Object.freeze 方法來凍結(jié)一個對象,一旦被凍結(jié)的對象就再也不能被修改了。
Tips:這里只是凍結(jié)了 users的值,引用不會被凍結(jié),當(dāng)我們需要 reactive 數(shù)據(jù)的時候,我們可以重新給 users 賦值。
export default { ? data: () => ({ ? ? users: {} ? }), ? async created() { ? ? ? const users = await axios.get("/api/users"); ? ? ? this.users = Object.freeze(users); ? }, ? methods:{ ? ? // 改變值不會觸發(fā)視圖響應(yīng) ? ? ? this.data.users[0] = newValue ? ? // 改變引用依然會觸發(fā)視圖響應(yīng) ? ? this.data.users = newArray ? } };
事件的銷毀
Vue 組件銷毀時,會自動清理它與其它實例的連接,解綁它的全部指令及事件監(jiān)聽器,但是僅限于組件本身的事件。如果在 js 內(nèi)
created() { ? addEventListener('click', this.click, false) }, beforeDestroy() { ? removeEventListener('click', this.click, false) }
第三方插件按需引入
我們在項目中經(jīng)常會需要引入第三方插件,如果我們直接引入整個插件,會導(dǎo)致項目的體積太大,我們可以借助 babel-plugin-component ,然后可以只引入需要的組件,以達(dá)到減小項目體積的目的。
React項目優(yōu)化
代碼層面的優(yōu)化
在constructor改變this指向代替箭頭函數(shù)和render內(nèi)綁定this,避免函數(shù)作為props帶來不必要的rerender
shouldComponentUpdate,減少不不必要的rerender
PureComponent高性能組件只響應(yīng)引用數(shù)據(jù)的深拷貝
合并setState操作,減少虛擬dom對比頻率
React路由動態(tài)加載react-loadable
避免使用Context
Context是react中跨組件樹傳遞數(shù)據(jù)的一種方法,但是會讓組件復(fù)用性變差,不推薦使用,有相應(yīng)場景的話就使用redux。
虛擬化長列表
當(dāng)頁面有非常多的元素時,會出現(xiàn)卡頓,這時可以使用虛擬滾動替代,僅渲染有限的內(nèi)容,降低重新渲染的時間,以及創(chuàng)建DOM節(jié)點的數(shù)量,推薦庫:react-window
key不要使用index
循環(huán)渲染時,數(shù)據(jù)變化頻繁的話,建議使用唯一的key,例如id。
多使用Memo、useMemo緩存
當(dāng)傳遞的數(shù)據(jù)發(fā)生變化時才會重新渲染。
組件卸載時清空還在執(zhí)行的方法
例如定時器、輪詢方法在卸載后還是會繼續(xù)執(zhí)行,卸載時要清空。
使用fragement或者空標(biāo)簽<></>避免額外標(biāo)簽
使用<Suspense /> 或者React.lazy懶加載,只支持default exports
盡量使用純組件,避免重復(fù)渲染
在構(gòu)造函數(shù)中進行函數(shù) this 綁定
避免使用內(nèi)聯(lián)樣式屬性
不要在render中改變應(yīng)用的狀態(tài)
為組件創(chuàng)造錯誤邊界
其他優(yōu)化方法
除了以上的一些優(yōu)化方法,還有從其他維度的優(yōu)化方向也可以對項目進行性能上的一些優(yōu)化
服務(wù)端渲染 SSR or 預(yù)渲染
服務(wù)端渲染是指 Vue 在客戶端將標(biāo)簽渲染成的整個 html 片段的工作在服務(wù)端完成,服務(wù)端形成的 html 片段直接返回給客戶端這個過程就叫做服務(wù)端渲染。
webpack層面的優(yōu)化
壓縮圖片媒體等靜態(tài)資源
減少 ES6 轉(zhuǎn)為 ES5 的冗余代碼
提取公共代碼
模版預(yù)編譯
提取組件的css
優(yōu)化SourceMap
構(gòu)建結(jié)果輸出分析
開啟gzip壓縮
gzip 是 GNUzip 的縮寫,最早用于 UNIX 系統(tǒng)的文件壓縮。HTTP 協(xié)議上的 gzip 編碼是一種用來改進 web 應(yīng)用程序性能的技術(shù),web 服務(wù)器和客戶端(瀏覽器)必須共同支持 gzip。目前主流的瀏覽器,Chrome,firefox,IE等都支持該協(xié)議。常見的服務(wù)器如 Apache,Nginx,IIS 同樣支持,gzip 壓縮效率非常高,通常可以達(dá)到 70% 的壓縮率,也就是說,如果你的網(wǎng)頁有 30K,壓縮之后就變成了 9K 左右。
瀏覽器緩存
為了提高用戶加載頁面的速度,對靜態(tài)資源進行緩存是非常必要的,根據(jù)是否需要重新向服務(wù)器發(fā)起請求來分類,將 HTTP 緩存規(guī)則分為兩大類(強制緩存,對比緩存),如果對緩存機制還不是了解很清楚的,可以參考作者寫的關(guān)于 HTTP 緩存的文章《深入理解HTTP緩存機制及原理》。
cdn
瀏覽器從服務(wù)器上下載 CSS、js 和圖片等文件時都要和服務(wù)器連接,而大部分服務(wù)器的帶寬有限,如果超過限制,網(wǎng)頁就半天反應(yīng)不過來。而 CDN 可以通過不同的域名來加載文件,從而使下載文件的并發(fā)連接數(shù)大大增加,且CDN 具有更好的可用性,更低的網(wǎng)絡(luò)延遲和丟包率 。
總結(jié)
以上總結(jié)了那么多的方法 當(dāng)然肯定還有許多其他方向的優(yōu)化啦,本人技術(shù)有限,肯定不能夠闡述完全,性能優(yōu)化是個大話題,有不同意見的小伙伴可以評論區(qū)討論一下