Vue 性能優(yōu)化

得益于 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)鍵詞玩法期待你的探索~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栗柒,一起剝皮案震驚了整個濱河市礁扮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞬沦,老刑警劉巖深员,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛙埂,居然都是意外死亡倦畅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門绣的,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叠赐,“玉大人欲账,你說我怎么就攤上這事“鸥牛” “怎么了赛不?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長罢洲。 經(jīng)常有香客問我踢故,道長,這世上最難降的妖魔是什么惹苗? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任殿较,我火速辦了婚禮,結(jié)果婚禮上桩蓉,老公的妹妹穿的比我還像新娘淋纲。我一直安慰自己,他們只是感情好院究,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布洽瞬。 她就那樣靜靜地躺著,像睡著了一般业汰。 火紅的嫁衣襯著肌膚如雪伙窃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天样漆,我揣著相機與錄音对供,去河邊找鬼。 笑死氛濒,一個胖子當(dāng)著我的面吹牛产场,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舞竿,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼京景,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骗奖?” 一聲冷哼從身側(cè)響起确徙,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎执桌,沒想到半個月后鄙皇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡仰挣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年伴逸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膘壶。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡错蝴,死狀恐怖洲愤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情顷锰,我是刑警寧澤柬赐,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站官紫,受9級特大地震影響肛宋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜束世,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一酝陈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧良狈,春花似錦后添、人聲如沸笨枯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馅精。三九已至严嗜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洲敢,已是汗流浹背漫玄。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留压彭,地道東北人睦优。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像壮不,于是被迫代替她去往敵國和親汗盘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355