得益于 Vue 的?響應(yīng)式系統(tǒng)?和?虛擬 DOM 系統(tǒng)?侠畔,Vue 在渲染組件的過程中能自動追蹤數(shù)據(jù)的依賴,并精確知曉數(shù)據(jù)更新的時候哪個組件需要重新渲染弦牡,渲染之后也會經(jīng)過虛擬 DOM diff 之后才會真正更新到 DOM 上坊饶,Vue 應(yīng)用的開發(fā)者一般不需要做額外的優(yōu)化工作。
但在實踐中仍然有可能遇到性能問題失都,下面會介紹一些定位分析 Vue 應(yīng)用性能問題的方式及一些優(yōu)化的建議。
整體內(nèi)容由三部分組成:
1幸冻、如何定位 Vue 應(yīng)用性能問題
2粹庞、Vue 應(yīng)用運行時性能優(yōu)化建議
3、Vue 應(yīng)用加載性能優(yōu)化建議
1. 如何定位 Vue 應(yīng)用性能問題
Vue 應(yīng)用的性能問題可以分為兩個部分,第一部分是運行時性能問題,第二部分是加載性能問題简逮。
和其他 web 應(yīng)用一樣,定位 Vue 應(yīng)用性能問題最好的工具是 Chrome Devtool强缘,通過 Performance 工具可以用來錄制一段時間的 CPU 占用督惰、內(nèi)存占用不傅、FPS 等運行時性能問題,通過 Network 工具可以用來分析加載性能問題赏胚。
例如访娶,通過 Performance 工具的 Bottom Up 標(biāo)簽我們可以看出一段時間內(nèi)耗時最多的操作,這對于優(yōu)化 CPU 占用和 FPS 過低非常有用觉阅,可以看出最為耗時的操作發(fā)生在哪里崖疤,可以知道具體函數(shù)的執(zhí)行時間秘车,定位到瓶頸之后,我們就可以做一些針對性的優(yōu)化劫哼。
2. Vue 應(yīng)用運行時性能優(yōu)化建議
運行時性能主要關(guān)注 Vue 應(yīng)用初始化之后對 CPU叮趴、內(nèi)存、本地存儲等資源的占用权烧,以及對用戶交互的及時響應(yīng)眯亦。下面是一些有用的優(yōu)化手段:
2.1 引入生產(chǎn)環(huán)境的 Vue 文件
開發(fā)環(huán)境下,Vue 會提供很多警告來幫你對付常見的錯誤與陷阱般码。而在生產(chǎn)環(huán)境下妻率,這些警告語句沒有用,反而會增加應(yīng)用的體積板祝。有些警告檢查還有一些小的運行時開銷宫静。
當(dāng)使用 webpack 或 Browserify 類似的構(gòu)建工具時,Vue 源碼會根據(jù) process.env.NODE_ENV 決定是否啟用生產(chǎn)環(huán)境模式券时,默認情況為開發(fā)環(huán)境模式孤里。在 webpack 與 Browserify 中都有方法來覆蓋此變量,以啟用 Vue 的生產(chǎn)環(huán)境模式革为,同時在構(gòu)建過程中警告語句也會被壓縮工具去除扭粱。
2.2 使用單文件組件預(yù)編譯模板
當(dāng)使用 DOM 內(nèi)模板或 JavaScript 內(nèi)的字符串模板時,模板會在運行時被編譯為渲染函數(shù)震檩。通常情況下這個過程已經(jīng)足夠快了琢蛤,但對性能敏感的應(yīng)用還是最好避免這種用法。
預(yù)編譯模板最簡單的方式就是使用單文件組件——相關(guān)的構(gòu)建設(shè)置會自動把預(yù)編譯處理好抛虏,所以構(gòu)建好的代碼已經(jīng)包含了編譯出來的渲染函數(shù)而不是原始的模板字符串博其。
2.3 提取組件的 CSS 到單獨到文件
當(dāng)使用單文件組件時,組件內(nèi)的 CSS 會以?<style>
?標(biāo)簽的方式通過 JavaScript 動態(tài)注入迂猴。這有一些小小的運行時開銷慕淡,將所有組件的 CSS 提取到同一個文件可以避免這個問題,也會讓 CSS 更好地進行壓縮和緩存沸毁。
查閱這個構(gòu)建工具各自的文檔來了解更多:
1峰髓、webpack + vue-loader (vue-cli
?的 webpack 模板已經(jīng)預(yù)先配置好)
2、Browserify + vueify
3息尺、Rollup + rollup-plugin-vue
2.4 利用Object.freeze()
提升性能
Object.freeze()
?可以凍結(jié)一個對象携兵,凍結(jié)之后不能向這個對象添加新的屬性,不能修改其已有屬性的值搂誉,不能刪除已有屬性徐紧,以及不能修改該對象已有屬性的可枚舉性、可配置性、可寫性并级。該方法返回被凍結(jié)的對象拂檩。
當(dāng)你把一個普通的 JavaScript 對象傳給 Vue 實例的 ?data
? 選項,Vue 將遍歷此對象所有的屬性嘲碧,并使用 ?Object.defineProperty? 把這些屬性全部轉(zhuǎn)為 getter/setter稻励,這些 getter/setter 對用戶來說是不可見的,但是在內(nèi)部它們讓 Vue 追蹤依賴愈涩,在屬性被訪問和修改時通知變化钉迷。
但 Vue 在遇到像?Object.freeze()
?這樣被設(shè)置為不可配置之后的對象屬性時,不
會為對象加上 setter getter 等數(shù)據(jù)劫持的方法钠署。參考 Vue 源碼
Vue observer 源碼
2.4.1 性能提升效果對比
在基于 Vue 的一個 big table benchmark 里糠聪,可以看到在渲染一個一個 1000 x 10 的表格的時候,開啟Object.freeze()
?前后重新渲染的對比谐鼎。
big table benchmark
開啟優(yōu)化之前
開啟優(yōu)化之后
在這個例子里舰蟆,使用了?Object.freeze()
比不使用快了 4 倍
2.4.2 為什么Object.freeze()
?的性能會更好
不使用Object.freeze()
?的CPU開銷
使用?Object.freeze()
的CPU開銷
對比可以看出,使用了?Object.freeze()
?之后狸棍,減少了 observer 的開銷身害。
2.4.3?Object.freeze()
應(yīng)用場景
由于?Object.freeze()
?會把對象凍結(jié),所以比較適合展示類的場景草戈,如果你的數(shù)據(jù)屬性需要改變塌鸯,可以重新替換成一個新的?Object.freeze()
的對象。
然后附上黃軼大大更加好的優(yōu)化方案:
2.5 扁平化 Store 數(shù)據(jù)結(jié)構(gòu)
很多時候唐片,我們會發(fā)現(xiàn)接口返回的信息是如下的深層嵌套的樹形結(jié)構(gòu):
{
??"id":?"123",
??"author":?{
????"id":?"1",
????"name":?"Paul"
??},
??"title":?"My?awesome?blog?post",
??"comments":?[
????{
??????"id":?"324",
??????"commenter":?{
????????"id":?"2",
????????"name":?"Nicole"
??????}
????}
??]
}
假如直接把這樣的結(jié)構(gòu)存儲在 store 中丙猬,如果想修改某個 commenter 的信息,我們需要一層層去遍歷找到這個用戶的信息费韭,同時有可能這個用戶的信息出現(xiàn)了多次茧球,還需要把其他地方的用戶信息也進行修改,每次遍歷的過程會帶來額外的性能開銷星持。
假設(shè)我們把用戶信息在 store 內(nèi)統(tǒng)一存放成?users[id]
這樣的結(jié)構(gòu)抢埋,修改和讀取用戶信息的成本就變得非常低。
你可以手動去把接口里的信息通過類似數(shù)據(jù)的表一樣像這樣存起來督暂,也可以借助一些工具揪垄,這里就需要提到一個概念叫做?JSON數(shù)據(jù)規(guī)范化(normalize)
, Normalizr 是一個開源的工具,可以將上面的深層嵌套的 JSON 對象通過定義好的 schema 轉(zhuǎn)變成使用 id 作為字典的實體表示的對象逻翁。
舉個例子饥努,針對上面的 JSON 數(shù)據(jù),我們定義?users
?comments
?articles
?三種 schema:
import?{normalize,?schema}?from?'normalizr';
//?定義?users?schema
const?user?=?new?schema.Entity('users');
//?定義?comments?schema
const?comment?=?new?schema.Entity('comments',?{
??commenter:?user,
});
//?定義?articles?schema
const?article?=?new?schema.Entity('articles',?{
??author:?user,
??comments:?[comment],
});
const?normalizedData?=?normalize(originalData,?article);
normalize 之后就可以得到下面的數(shù)據(jù)卢未,我們可以按照這種形式存放在 store 中肪凛,之后想修改和讀取某個 id 的用戶信息就變得非常高效了,時間復(fù)雜度降低到了 O(1)辽社。
{
??result:?"123",
??entities:?{
????"articles":?{
??????"123":?{
????????id:?"123",
????????author:?"1",
????????title:?"My?awesome?blog?post",
????????comments:?[?"324"?]
??????}
????},
????"users":?{
??????"1":?{?"id":?"1",?"name":?"Paul"?},
??????"2":?{?"id":?"2",?"name":?"Nicole"?}
????},
????"comments":?{
??????"324":?{?id:?"324",?"commenter":?"2"?}
????}
??}
}
2.6 避免持久化 Store 數(shù)據(jù)帶來的性能問題
當(dāng)你有讓 Vue App 離線可用伟墙,或者有接口出錯時候進行災(zāi)備的需求的時候,你可能會選擇把 Store 數(shù)據(jù)進行持久化滴铅,這個時候需要注意以下幾個方面:
2.6.1 持久化時寫入數(shù)據(jù)的性能問題
Vue 社區(qū)中比較流行的 vuex-persistedstate戳葵,利用了 store 的 subscribe 機制,來訂閱 Store 數(shù)據(jù)的 mutation汉匙,如果發(fā)生了變化拱烁,就會寫入 storage 中,默認用的是 localstorage 作為持久化存儲噩翠。
也就是說默認情況下每次 commit 都會向 localstorage 寫入數(shù)據(jù)戏自,localstorage 寫入是同步的,而且存在不小的性能開銷伤锚,如果你想打造 60fps 的應(yīng)用擅笔,就必須避免頻繁寫入持久化數(shù)據(jù)
下面是開發(fā)環(huán)境下通過 Performance 工具抓取的一個截圖,可以看到出現(xiàn)了一次長達 6s 的卡頓:
6秒鐘的卡頓
通過 Bottom-Up 可以看到 setState 占用了 3241.4ms 的 CPU 執(zhí)行時間屯援,而 setState 正是在向 Storage 寫入數(shù)據(jù)猛们。
vuex-persistedstate setState 源碼
我們應(yīng)該盡量減少直接寫入 Storage 的頻率:
1、多次寫入操作合并為一次狞洋,比如采用函數(shù)節(jié)流或者將數(shù)據(jù)先緩存在內(nèi)存中弯淘,最后在一并寫入
2、只有在必要的時候才寫入吉懊,比如只有關(guān)心的模塊的數(shù)據(jù)發(fā)生變化的時候才寫入
2.6.2 避免持久化存儲的容量持續(xù)增長
由于持久化緩存的容量有限庐橙,比如 localstorage 的緩存在某些瀏覽器只有 5M,我們不能無限制的將所有數(shù)據(jù)都存起來借嗽,這樣很容易達到容量限制怕午,同時數(shù)據(jù)過大時,讀取和寫入操作會增加一些性能開銷淹魄,同時內(nèi)存也會上漲郁惜。
尤其是將 API 數(shù)據(jù)進行 normalize 數(shù)據(jù)扁平化后之后,會將一份數(shù)據(jù)散落在不同的實體上甲锡,下次請求到新的數(shù)據(jù)也會散落在其他不同的實體上兆蕉,這樣會帶來持續(xù)的存儲增長。
因此缤沦,當(dāng)設(shè)計了一套持久化的數(shù)據(jù)緩存策略的時候虎韵,同時應(yīng)該設(shè)計舊數(shù)據(jù)的緩存清除策略,例如請求到新數(shù)據(jù)的時候?qū)⑴f的實體逐個進行清除缸废。
2.7 優(yōu)化無限列表性能
如果你的應(yīng)用存在非常長或者無限滾動的列表包蓝,那么采用?窗口化?的技術(shù)來優(yōu)化性能驶社,只需要渲染少部分區(qū)域的內(nèi)容,減少重新渲染組件和創(chuàng)建 dom 節(jié)點的時間测萎。
vue-virtual-scroll-list 和 vue-virtual-scroller 都是解決這類問題的開源項目亡电。你也可以參考 Google 工程師的文章Complexities of an Infinite Scroller 來嘗試自己實現(xiàn)一個虛擬的滾動列表來優(yōu)化性能,主要使用到的技術(shù)是 DOM 回收硅瞧、墓碑元素和滾動錨定份乒。
Google 工程師繪制的無限列表設(shè)計
2.8 通過組件懶加載優(yōu)化超長應(yīng)用內(nèi)容初始渲染性能
上面提到的無限列表的場景,比較適合列表內(nèi)元素非常相似的情況腕唧,不過有時候或辖,你的 Vue 應(yīng)用的超長列表內(nèi)的內(nèi)容往往不盡相同,例如在一個復(fù)雜的應(yīng)用的主界面中颂暇,整個主界面由非常多不同的模塊組成,而用戶看到的往往只有首屏一兩個模塊但惶。在初始渲染的時候不可見區(qū)域的模塊也會執(zhí)行和渲染蟀架,帶來一些額外的性能開銷。
使用組件懶加載在不可見時只需要渲染一個骨架屏榆骚,不需要真正渲染組件
你可以對組件直接進行懶加載片拍,對于不可見區(qū)域的組件內(nèi)容,直接不進行加載和初始化妓肢,避免初始化渲染運行時的開銷捌省。具體可以參考我們之前的專欄文章 性能優(yōu)化之組件懶加載: Vue Lazy Component 介紹,了解如何做到組件粒度的懶加載碉钠。
3. Vue 應(yīng)用加載性能優(yōu)化建議
3.1 利用服務(wù)端渲染(SSR)和預(yù)渲染(Prerender)來優(yōu)化加載性能
在一個單頁應(yīng)用中纲缓,往往只有一個 html 文件,然后根據(jù)訪問的 url 來匹配對應(yīng)的路由腳本喊废,動態(tài)地渲染頁面內(nèi)容祝高。單頁應(yīng)用比較大的問題是首屏可見時間過長。
單頁面應(yīng)用顯示一個頁面會發(fā)送多次請求污筷,第一次拿到 html 資源工闺,然后通過請求再去拿數(shù)據(jù),再將數(shù)據(jù)渲染到頁面上瓣蛀。而且由于現(xiàn)在微服務(wù)架構(gòu)的存在陆蟆,還有可能發(fā)出多次數(shù)據(jù)請求才能將網(wǎng)頁渲染出來,每次數(shù)據(jù)請求都會產(chǎn)生 RTT(往返時延)惋增,會導(dǎo)致加載頁面的時間拖的很長叠殷。
服務(wù)端渲染、預(yù)渲染和客戶端渲染的對比
這種情況下可以采用服務(wù)端渲染(SSR)和預(yù)渲染(Prerender)來提升加載性能诈皿,這兩種方案林束,用戶讀取到的直接就是網(wǎng)頁內(nèi)容像棘,由于少了節(jié)省了很多 RTT(往返時延),同時壶冒,還可以對一些資源內(nèi)聯(lián)在頁面缕题,可以進一步提升加載的性能。
服務(wù)端渲染(SSR)可以考慮使用 Nuxt 或者按照 Vue 官方提供的 Vue SSR 指南來一步步搭建依痊。
3.2 通過組件懶加載優(yōu)化超長應(yīng)用內(nèi)容加載性能
在上面提到的超長應(yīng)用內(nèi)容的場景中,通過組件懶加載方案可以優(yōu)化初始渲染的運行性能怎披,其實胸嘁,這對于優(yōu)化應(yīng)用的加載性能也很有幫助。
組件粒度的懶加載結(jié)合異步組件和 webpack 代碼分片凉逛,可以保證按需加載組件性宏,以及組件依賴的資源、接口請求等状飞,比起通常單純的對圖片進行懶加載毫胜,更進一步的做到了按需加載資源。
使用組件懶加載之前的請求瀑布圖
使用組件懶加載之后的請求瀑布圖
使用組件懶加載方案對于超長內(nèi)容的應(yīng)用初始化渲染很有幫助诬辈,可以減少大量必要的資源請求酵使,縮短渲染關(guān)鍵路徑。
總結(jié)
本文總結(jié)了 Vue 應(yīng)用運行時以及加載時的一些性能優(yōu)化措施焙糟,下面做一個回顧和概括:
1口渔、Vue 應(yīng)用運行時性能優(yōu)化措施
(1)引入生產(chǎn)環(huán)境的 Vue 文件
(2)使用單文件組件預(yù)編譯模板
(3)提取組件的 CSS 到單獨到文件
(4)利用Object.freeze()
提升性能
(5)扁平化 Store 數(shù)據(jù)結(jié)構(gòu)
(6)合理使用持久化 Store 數(shù)據(jù)
(7)組件懶加載
2、Vue 應(yīng)用加載性能優(yōu)化措施
(1)服務(wù)端渲染 / 預(yù)渲染
(2)組件懶加載
文章總結(jié)的這些性能優(yōu)化手段當(dāng)然不能覆蓋所有的 Vue 應(yīng)用性能問題穿撮,我們也會不斷總結(jié)和補充其他問題及優(yōu)化措施缺脉,希望文章中提到這些實踐經(jīng)驗?zāi)芙o你的 Vue 應(yīng)用性能優(yōu)化工作帶來小小的幫助。
作者:迅雷前端
鏈接:https://juejin.im/post/5b960fcae51d450e9d645c5f
來源:掘金
關(guān)注公眾號【grain先森】悦穿,回復(fù)關(guān)鍵詞 【18福利】攻礼,獲取為你準(zhǔn)備的年終福利,更多關(guān)鍵詞玩法期待你的探索~