定位應(yīng)用的性能問題
Vue應(yīng)用的性能問題可以分為兩個(gè)部分:運(yùn)行時(shí)性能問題,加載性能問題。
和其他 web應(yīng)用一樣,定位 Vue應(yīng)用性能問題最好的工具是 Chrome Devtool(F12 谷歌開發(fā)者工具),通過 Performance 工具可以用來錄制一段時(shí)間的 CPU 占用、內(nèi)存占用、FPS 等運(yùn)行時(shí)性能問題,通過 Network 工具可以用來分析加載性能問題。
Chrome Performance 常見的名詞指標(biāo)

FP:First Paint首次繪制,第一幀數(shù)據(jù)渲染出來時(shí)
標(biāo)記瀏覽器渲染任何在視覺上不同于導(dǎo)航前的屏幕內(nèi)容的時(shí)間點(diǎn)FCP:First Contentful Paint首次內(nèi)容繪制
標(biāo)記瀏覽器渲染來自DOM第一位內(nèi)容的時(shí)間點(diǎn),該內(nèi)容可能是文本、圖像、SVG,元素LCP:Largest Contentful Paint最大內(nèi)容渲染,2019.11新增
代表在viewport中最大的頁面元素加載的事件。LCP的數(shù)據(jù)會(huì)通過PerformanceEntry對(duì)象記錄,每次出現(xiàn)更大的內(nèi)容渲染 則會(huì)產(chǎn)生一個(gè)新的PerformanceEntry對(duì)象DCL:DomContentLoaded
當(dāng)HTML文檔被完全加載和解析完成之后,DomContentLoaded事件被觸發(fā),無需等待樣式表、圖像和子框架的完成加載。FMP:First Meaningful Paint首次有效繪制
主內(nèi)容的繪制,視頻網(wǎng)站的主角元素自然是視頻,微博網(wǎng)站的博文是主要元素L:onLoad
當(dāng)依賴的資源全部加載完成后才會(huì)觸發(fā)TTI:Time to Interactive可交互時(shí)間
用于標(biāo)記程序已進(jìn)行視覺渲染并能可靠響應(yīng)用戶輸入的時(shí)間點(diǎn)TBT:Total Blocking Time頁面阻塞總時(shí)長
匯總所有加載過程中阻塞用戶操作的時(shí)長,在FCP和TTI之間任何long task(執(zhí)行時(shí)間超過一定閾值,如50ms)中阻塞部分都會(huì)被匯總FID:First Input Delay首次輸入延遲
衡量從用戶首次與網(wǎng)站交互 到 瀏覽器實(shí)際能夠訪問 之間的時(shí)間SI:Speed Index
用于顯示頁面可見部分的顯示速度(時(shí)間)
我們通常要關(guān)心指標(biāo):FP、FCP、FMP
首頁白屏優(yōu)化
對(duì)于打包后的SPA程序,index.html是一個(gè)空的,所以首次渲染的頁面也是空的(FP),也就是白屏狀態(tài);
等待JS加載完成,異步請(qǐng)求數(shù)據(jù),在未來的某一幀才開始渲染出大體內(nèi)容架構(gòu)(FCP),但仍缺少一些圖片等資源,且無法交互;
從而可知,為了減少白屏?xí)r間,可以把 FCP 階段提前:
- 骨架屏策略:在上線之前,通過
JS或框架在index.html中插入大體的頁面結(jié)構(gòu); - 預(yù)渲染:靜態(tài)渲染,對(duì)于不變的內(nèi)容在本地預(yù)渲染,而變化的內(nèi)容預(yù)留占位。
資源優(yōu)化
prefetch
prefetch 預(yù)獲取,分為三種類型:link prefetch、dns prefetch、prerender
-
link prefetch
在瀏覽器空閑時(shí)加載一個(gè)資源(HTML、JS、CSS、Image、Font),是真正的資源下載;
注意:雖然預(yù)獲取了,但頁面不會(huì)解析,<link ref="prefetch" > //谷歌 <link ref="prefetch" href="/uploads/images/bg.png">JS不會(huì)被執(zhí)行。 -
dns prefetch
前端優(yōu)化與DNS相關(guān)的有兩點(diǎn):減少DNS的請(qǐng)求次數(shù),DNS預(yù)解析
提前解析當(dāng)前頁面中與當(dāng)前域名不在同一個(gè)域的域名(第三方域名),并緩存結(jié)果,但不會(huì)下載任何資源,所以寫入具體的<!-- 開啟DNS預(yù)獲取,好像也不需要 --> <meta http-equiv="x-dns-prefetch-control" content="on"> <!-- 設(shè)置DNS預(yù)解析的域名 --> <link rel="dns-prefetch" />JS、Image等資源沒有意義。
當(dāng)用戶真正點(diǎn)擊網(wǎng)頁上的域名鏈接時(shí),DNS早已在后臺(tái)解析完成,所以能減少等待時(shí)間,提升用戶體驗(yàn)。 -
prerender預(yù)渲染
不僅會(huì)加載資源,還會(huì)解析并預(yù)渲染頁面。但是否執(zhí)行預(yù)渲染是根據(jù)瀏覽器自身判斷的,瀏覽器可能會(huì):- 分配少量資源對(duì)頁面進(jìn)行預(yù)渲染;
- 掛起部分請(qǐng)求直至頁面可見時(shí);
- 可能放棄預(yù)渲染,如果消耗資源過多等等情況。。。
很顯然,它是一個(gè)相對(duì)非?!爸亍钡牟僮鳎ㄙY源浪費(fèi)嚴(yán)重),瀏覽器會(huì)提前加載所有資源,并把渲染結(jié)果緩存在內(nèi)存中。<link rel="prerender" />
在SPA項(xiàng)目中基本沒有應(yīng)用場景,對(duì)于MPA項(xiàng)目,在確定用戶一定會(huì)進(jìn)入該頁面的情況下,可以考慮使用prerender。
preconnect
preconnect 預(yù)連接
<!-- 谷歌使用了預(yù)鏈接 -->
<link ref="preconnect" >
瀏覽器要建立一個(gè)連接,需要經(jīng)過DNS解析,TCP三次握手和TLS協(xié)商(https),這些過程也相當(dāng)?shù)暮臅r(shí)。dns-prefetch只是做了DNS解析,而preconnect把這三步都做了,使瀏覽器預(yù)先建立一個(gè)連接,等真正需要加載資源時(shí)能直接請(qǐng)求。
preload
preload 預(yù)加載,它的作用是將資源率先加載,聽起來容易和prefetch混淆:
-
prefetch是預(yù)獲取,是對(duì)用戶接下來很可能會(huì)使用到的資源的預(yù)先下載; -
preload本質(zhì)上是影響資源的加載順序,把可能后置下載的資源前置下載。
preload的特點(diǎn)
- 具有優(yōu)先級(jí),但不會(huì)阻塞
onload事件:preload在網(wǎng)頁中具有強(qiáng)制加載的功能,所以它的加載具有優(yōu)先級(jí),不過它僅僅是加載資源,并不會(huì)執(zhí)行,所以仍需要script加載資源; - 它設(shè)計(jì)的目的是為當(dāng)前頁面的資源進(jìn)行預(yù)加載,跳轉(zhuǎn)頁面后就使用不到了;
- 使用
as字段來設(shè)定優(yōu)先級(jí),as=style則為最高優(yōu)先級(jí)。優(yōu)先級(jí)順序?yàn)椋?code>HTML/CSS>Images>JS
舉個(gè)栗子
當(dāng)資源沒有直接體現(xiàn)在HTML中,而是隱藏在CSS或JS里,preload可以提前告知瀏覽器隱藏資源的存在,以便瀏覽器做出最優(yōu)的安排。
# style.css
@font-face {
font-family: myFirstFont;
src: url('https://fonts.gstatic.com/s/sofia/v8/8QIHdirahM3j_su5uI0Orbjl.woff2');
}
h1 {
font-family: myFirstFont;
}
- 未配置
preload
加載過程<head> <link rel="stylesheet" href="style.css"> </head>

對(duì)字體的下載發(fā)生在CSS下載之后,因?yàn)橹挥挟?dāng)瀏覽器下載完CSS并解析之后,才知道字體資源的存在;
- 配置上
preload
加載過程<link rel="preload" as="font" crossorigin="anonymous"> <link rel="stylesheet" href="style.css">

同時(shí)下載字體和CSS,因?yàn)闉g覽器提前知道了隱藏資源的存在,做出了最優(yōu)安排。與未配置preload相比,下載時(shí)間減少了。
小結(jié)
- 如果出現(xiàn)跨域無法加載,則加上
crossorigin字段; - 這些優(yōu)化都是屬于勤快優(yōu)化類型,將數(shù)據(jù)的加載和數(shù)據(jù)的使用/解析分開,且都有完整的工程化打包方案,如
webpack,很多大站都會(huì)使用這些優(yōu)化方式。
優(yōu)化無限列表性能
如果應(yīng)用中存在非常長或者無限滾動(dòng)的列表,那么采用 窗口化 的技術(shù)來優(yōu)化性能,只需要渲染少部分區(qū)域的內(nèi)容,減少重新渲染組件和創(chuàng)建 DOM 節(jié)點(diǎn)的時(shí)間。
vue-virtual-scroll-list 和 vue-virtual-scroller 都是解決這類問題的開源項(xiàng)目。還可以參考 Google工程師的文章Complexities of an Infinite Scroller來嘗試自己實(shí)現(xiàn)一個(gè)虛擬的滾動(dòng)列表來優(yōu)化性能,主要使用到的技術(shù)是 DOM 回收、墓碑元素 和 滾動(dòng)錨定。
組件懶加載
上面提到的無限列表的場景,比較適合列表內(nèi)元素非常相似的情況。不過有時(shí)候,你的Vue應(yīng)用的超長列表中的內(nèi)容往往不盡相同,例如在一個(gè)復(fù)雜應(yīng)用的主界面中,由非常多不同的模塊組成,而用戶看到的往往只有首屏一兩個(gè)模塊。但在初始渲染時(shí),不可見區(qū)域的模塊也會(huì)執(zhí)行和渲染,從而帶來一些額外的性能開銷。
使用組件懶加載在不可見時(shí)只需要渲染一個(gè)骨架屏,不需要真正渲染組件。
懶加載也叫延遲加載,即在需要的時(shí)候進(jìn)行加載,隨用隨載;
-
實(shí)現(xiàn)思路:
- 組件化
將各模塊拆分為組件粒度,降低耦合度;將組件依賴的資源(比如請(qǐng)求接口、請(qǐng)求相關(guān)的依賴資源)全部封裝在組件內(nèi)部進(jìn)行調(diào)用; - 加載優(yōu)先級(jí)
優(yōu)先加載首屏可見模塊,其余不可見模塊懶加載,待可見或即將可見時(shí)加載;
- 組件化
判斷可見性問題
通過監(jiān)聽scroll、resize事件來判斷模塊是否可見,代碼不僅繁瑣,而且一不小心沒有函數(shù)去抖就又可能導(dǎo)致嚴(yán)重的性能問題。
H5新增的IntersectionObserver API是一個(gè)不錯(cuò)的解決方案,其設(shè)計(jì)是異步的,回調(diào)是在主線程空閑時(shí)才執(zhí)行,而且保證回調(diào)執(zhí)行次數(shù)非常有限,在性能方面表現(xiàn)更優(yōu),使用起來也更簡單。
當(dāng)然,低版本瀏覽器還是要通過polyfill兼容。盡可能懶的條件渲染
解決了可見性問題,懶加載就比較簡單了,Vue提供的v-if可以做到惰性渲染。-
如果可見后進(jìn)行初始渲染,可見前如何顯示
如果在判斷加載條件為false時(shí),什么都不渲染,就會(huì)帶來一系列問題:- 用戶體驗(yàn)比較差,最開始是白屏,然后突然又渲染出現(xiàn)內(nèi)容;
- 最致命的是,判斷可見性需要一個(gè)目標(biāo)來觀察,如果什么不都渲染,那就無從觀察。
因此,引入一個(gè)骨架屏的概念,為真實(shí)的組件創(chuàng)建一個(gè)在尺寸、樣式上非常接近真實(shí)組件的組件。
骨架屏的作用:- 提升用戶感知體驗(yàn);
- 保證切換的一致性;
- 提供可見性觀察的目標(biāo)對(duì)象。

如何提升切換時(shí)的體驗(yàn)
在真實(shí)組件開始渲染時(shí),需要一定的時(shí)間和空間,時(shí)間指的是真實(shí)組件從創(chuàng)建到渲染的時(shí)間,包括請(qǐng)求接口、請(qǐng)求資源和渲染的時(shí)間,空間指的是頁面布局中需要給真實(shí)組件留出剛好的位置,避免產(chǎn)生抖動(dòng)。
我們可以使用Vue內(nèi)置的transition組件自定義骨架組件和真實(shí)組件的進(jìn)入和離開效果,通過合理的布局和定位,減少切換時(shí)的抖動(dòng),通過設(shè)置過渡效果給真實(shí)組件留出一定的加載時(shí)間。Vue組件懶加載方案
--- Vue Lazy Component
該插件支持 組件可見或即將可見時(shí)懶加載,支持 組件延時(shí)加載,支持 加載組件前展示組件骨架,提高用戶體驗(yàn),支持 懶加載組件分包異步加載。
API上的優(yōu)化
-
Object.freeze
Object.freeze():把不會(huì)修改的對(duì)象/數(shù)組冷凍起來,Vue將不會(huì)對(duì)這些數(shù)據(jù)做響應(yīng)式處理。當(dāng)然,擅自修改這些數(shù)據(jù)也將會(huì)報(bào)錯(cuò)給你看。const columnList = Object.freeze([ { title: '姓名', key: 'name', align: 'center' }, { title: '性別', key: 'gender', align: 'center' } ])