前言
移動互聯(lián)網(wǎng)時代,用戶對于網(wǎng)頁的打開速度要求越來越高内贮。首屏作為直面用戶的第一屏宣赔,其重要性不言而喻。優(yōu)化用戶體驗更是我們前端開發(fā)非常需要 focus 的東西之一侣姆。
從用戶的角度而言砌左,當打開一個網(wǎng)頁,往往關心的是從輸入完網(wǎng)頁地址后到最后展現(xiàn)完整頁面這個過程需要的時間铺敌,這個時間越短汇歹,用戶體驗越好。所以作為網(wǎng)頁的開發(fā)者偿凭,就從輸入url到頁面渲染呈現(xiàn)這個過程中去提升網(wǎng)頁的性能产弹。
所以輸入URL后發(fā)生了什么呢?在瀏覽器中輸入url會經(jīng)歷域名解析、建立TCP連接痰哨、發(fā)送http請求胶果、資源解析等步驟。
http緩存優(yōu)化是網(wǎng)頁性能優(yōu)化的重要一環(huán)斤斧,這一部分我會在后續(xù)筆記中做一個詳細總結早抠,所以本文暫不多做詳細整理。本文主要從網(wǎng)頁渲染過程撬讽、網(wǎng)頁交互以及Vue應用優(yōu)化三個角度對性能優(yōu)化做一個小結蕊连。
一、頁面加載及渲染過程優(yōu)化
瀏覽器渲染流程
首先談談拿到服務端資源后瀏覽器渲染的流程:
1\. 解析 HTML 文件游昼,構建 DOM 樹甘苍,同時瀏覽器主進程負責下載 CSS 文件
2\. CSS 文件下載完成,解析 CSS 文件成樹形的數(shù)據(jù)結構烘豌,然后結合 DOM 樹合并成 RenderObject 樹
3\. 布局 RenderObject 樹 (Layout/reflow)载庭,負責 RenderObject 樹中的元素的尺寸,位置等計算
4\. 繪制 RenderObject 樹 (paint)廊佩,繪制頁面的像素信息
5\. 瀏覽器主進程將默認的圖層和復合圖層交給 GPU 進程囚聚,GPU 進程再將各個圖層合成(composite),最后顯示出頁面
CRP(關鍵渲染路徑Critical Rendering Path)優(yōu)化
關鍵渲染路徑是瀏覽器將 HTML标锄、CSS靡挥、JavaScript 轉(zhuǎn)換為在屏幕上呈現(xiàn)的像素內(nèi)容所經(jīng)歷的一系列步驟。也就是我們剛剛提到的的的瀏覽器渲染流程鸯绿。
為盡快完成首次渲染跋破,我們需要最大限度減小以下三種可變因素:
* 關鍵資源的數(shù)量: 可能阻止網(wǎng)頁首次渲染的資源。
* 關鍵路徑長度: 獲取所有關鍵資源所需的往返次數(shù)或總時間瓶蝴。
* 關鍵字節(jié): 實現(xiàn)網(wǎng)頁首次渲染所需的總字節(jié)數(shù)毒返,等同于所有關鍵資源傳送文件大小的總和。
優(yōu)化 DOM
* 刪除不必要的代碼和注釋包括空格舷手,盡量做到最小化文件拧簸。
* 可以利用 GZIP 壓縮文件。
* 結合 HTTP 緩存文件男窟。
優(yōu)化 CSSOM
首先盆赤,DOM 和 CSSOM 通常是并行構建的,所以 CSS 加載不會阻塞 DOM 的解析歉眷。
然而牺六,由于 Render Tree 是依賴于 DOM Tree 和 CSSOM Tree 的,
所以他必須等待到 CSSOM Tree 構建完成汗捡,也就是 CSS 資源加載完成(或者 CSS 資源加載失敗)后淑际,才能開始渲染。因此,CSS 加載會阻塞 Dom 的渲染春缕。
由此可見盗胀,對于 CSSOM 縮小、壓縮以及緩存同樣重要锄贼,我們可以從這方面考慮去優(yōu)化票灰。
* 減少關鍵 CSS 元素數(shù)量
* 當我們聲明樣式表時,請密切關注媒體查詢的類型宅荤,它們極大地影響了 CRP 的性能 屑迂。
優(yōu)化 JavaScript
當瀏覽器遇到 script 標記時,會阻止解析器繼續(xù)操作膘侮,直到 CSSOM 構建完畢屈糊,JavaScript 才會運行并繼續(xù)完成 DOM 構建過程的榛。
* async: 當我們在 script 標記添加 async 屬性以后琼了,瀏覽器遇到這個 script 標記時會繼續(xù)解析 DOM,同時腳本也不會被 CSSOM 阻止夫晌,即不會阻止 CRP雕薪。
* defer: 與 async 的區(qū)別在于,腳本需要等到文檔解析后( DOMContentLoaded 事件前)執(zhí)行晓淀,而 async 允許腳本在文檔解析時位于后臺運行(兩者下載的過程不會阻塞 DOM所袁,但執(zhí)行會)。
* 當我們的腳本不會修改 DOM 或 CSSOM 時凶掰,推薦使用 async 燥爷。
* 預加載 —— preload & prefetch 。
* DNS 預解析 —— dns-prefetch 懦窘。
小結
* 分析并用 **關鍵資源數(shù) 關鍵字節(jié)數(shù) 關鍵路徑長度** 來描述我們的 CRP 前翎。
* 最小化關鍵資源數(shù): 消除它們(內(nèi)聯(lián))、推遲它們的下載(defer)或者使它們異步解析(async)等等 畅涂。
* 優(yōu)化關鍵字節(jié)數(shù)(縮小港华、壓縮)來減少下載時間 。
* 優(yōu)化加載剩余關鍵資源的順序: 讓關鍵資源(CSS)盡早下載以減少 CRP 長度 午衰。
補充閱讀: 前端性能優(yōu)化之關鍵路徑渲染優(yōu)化
瀏覽器重繪(Repaint)和回流(Reflow)
回流必將引起重繪立宜,重繪不一定會引起回流。
重繪(Repaint)
當頁面中元素樣式的改變并不影響它在文檔流中的位置時(例如:color臊岸、background-color橙数、visibility 等),瀏覽器會將新樣式賦予給元素并重新繪制它帅戒,這個過程稱為重繪商模。
回流(Reflow)
當 Render Tree 中部分或全部元素的尺寸、結構、或某些屬性發(fā)生改變時施流,瀏覽器重新渲染部分或全部文檔的過程稱為回流响疚。
會導致回流的操作:
* 頁面首次渲染
* 瀏覽器窗口大小發(fā)生改變
* 元素尺寸或位置發(fā)生改變元素內(nèi)容變化(文字數(shù)量或圖片大小等等)
* 元素字體大小變化
* 添加或者刪除可見的 DOM 元素
* 激活 CSS 偽類(例如:hover)
* 查詢某些屬性或調(diào)用某些方法
* 一些常用且會導致回流的屬性和方法
clientWidth、clientHeight瞪醋、clientTop忿晕、clientLeftoffsetWidth、offsetHeight银受、offsetTop践盼、offsetLeftscrollWidth、scrollHeight宾巍、scrollTop咕幻、scrollLeftscrollIntoView()、scrollIntoViewIfNeeded()顶霞、getComputedStyle()肄程、
getBoundingClientRect()、scrollTo()
性能影響
回流比重繪的代價要更高选浑。
有時即使僅僅回流一個單一的元素蓝厌,它的父元素以及任何跟隨它的元素也會產(chǎn)生回流。現(xiàn)代瀏覽器會對頻繁的回流或重繪操作進行優(yōu)化:瀏覽器會維護一個隊列古徒,把所有引起回流和重繪的操作放入隊列中拓提,如果隊列中的任務數(shù)量或者時間間隔達到一個閾值的,瀏覽器就會將隊列清空隧膘,進行一次批處理代态,這樣可以把多次回流和重繪變成一次。
當你訪問以下屬性或方法時疹吃,瀏覽器會立刻清空隊列:
clientWidth蹦疑、clientHeight、clientTop互墓、clientLeft
offsetWidth必尼、offsetHeight、offsetTop篡撵、offsetLeft
scrollWidth判莉、scrollHeight、scrollTop育谬、scrollLeft
width券盅、height
getComputedStyle()
getBoundingClientRect()
因為隊列中可能會有影響到這些屬性或方法返回值的操作,即使你希望獲取的信息與隊列中操作引發(fā)的改變無關膛檀,瀏覽器也會強行清空隊列锰镀,確保你拿到的值是最精確的娘侍。
如何避免
CSS
- 避免使用 table 布局。
- 盡可能在 DOM 樹的最末端改變 class泳炉。
- 避免設置多層內(nèi)聯(lián)樣式憾筏。
- 將動畫效果應用到 position 屬性為 absolute 或 fixed 的元素上。
- 避免使用 CSS 表達式(例如:calc())花鹅。
Javascript
- 避免頻繁操作樣式氧腰,最好一次性重寫 style 屬性,或者將樣式列表定義為 class 并一次性更改 class 屬性刨肃。
// 優(yōu)化前
const el = document.getElementById('test');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 優(yōu)化后,一次性修改樣式古拴,這樣可以將三次重排減少到一次重排
const el = document.getElementById('test');
el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'
- 避免頻繁操作 DOM,創(chuàng)建一個 documentFragment真友,在它上面應用所有 DOM 操作黄痪,最后再把它添加到文檔中。
- 也可以先為元素設置 display: none盔然,操作結束后再把它顯示出來桅打。因為在 display 屬性為 none 的元素上進行的 DOM 操作不會引發(fā)回流和重繪。
- 避免頻繁讀取會引發(fā)回流/重繪的屬性轻纪,如果確實需要多次使用油额,就用一個變量緩存起來叠纷。
- 對具有復雜動畫的元素使用絕對定位刻帚,使它脫離文檔流,否則會引起父元素及后續(xù)元素頻繁回流涩嚣。
圖片懶加載
圖片懶加載在一些圖片密集型的網(wǎng)站中運用比較多崇众,通過圖片懶加載可以讓一些不可視的圖片不去加載,避免一次性加載過多的圖片導致請求阻塞(瀏覽器一般對同一域名下的并發(fā)請求的連接數(shù)有限制)航厚,這樣就可以提高網(wǎng)站的加載速度顷歌,提高用戶體驗。
原理
將頁面中的img標簽src指向一張小圖片或者src為空幔睬,然后定義data-src(這個屬性可以自定義命名眯漩,我才用data-src)屬性指向真實的圖片。src指向一張默認的圖片麻顶,否則當src為空時也會向服務器發(fā)送一次請求赦抖。可以指向loading的地址辅肾。注意队萤,圖片要指定寬高。
<img src="default.jpg" data-src="666.jpg" />
當載入頁面時矫钓,先把可視區(qū)域內(nèi)的img標簽的data-src屬性值負給src要尔,然后監(jiān)聽滾動事件舍杜,把用戶即將看到的圖片加載。這樣便實現(xiàn)了懶加載赵辕。
實例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
img {
display: block;
margin-bottom: 50px;
width: 400px;
height: 400px;
}
</style>
</head>
<body>
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<script>
let num = document.getElementsByTagName('img').length;
let img = document.getElementsByTagName("img");
let n = 0; //存儲圖片加載到的位置既绩,避免每次都從第一張圖片開始遍歷
lazyload(); //頁面載入完畢加載可是區(qū)域內(nèi)的圖片
window.onscroll = lazyload;
function lazyload() { //監(jiān)聽頁面滾動事件
let seeHeight = document.documentElement.clientHeight; //可見區(qū)域高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
for (let i = n; i < num; i++) {
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttribute("src") == "Go.png") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
</script>
</body>
</html>
事件委托
事件委托其實就是利用JS事件冒泡機制把原本需要綁定在子元素的響應事件(click、keydown……)委托給父元素还惠,讓父元素擔當事件監(jiān)聽的職務熬词。事件代理的原理是DOM元素的事件冒泡。
優(yōu)點:
1\. 大量減少內(nèi)存占用吸重,減少事件注冊互拾。
2\. 新增元素實現(xiàn)動態(tài)綁定事件
例如有一個列表需要綁定點擊事件,每一個列表項的點擊都需要返回不同的結果嚎幸。
傳統(tǒng)寫法:
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>green</li>
<li>black</li>
<li>white</li>
</ul>
<script>
(function () {
var color_list = document.querySelectorAll('li')
console.log("color_list", color_list)
for (let item of color_list) {
item.onclick = showColor;
}
function showColor(e) {
alert(e.target.innerHTML)
console.log("showColor -> e.target", e.target.innerHTML)
}
})();
</script>
傳統(tǒng)方法會利用for循環(huán)遍歷列表為每一個列表元素綁定點擊事件颜矿,當列表中元素數(shù)量非常龐大時,需要綁定大量的點擊事件嫉晶,這種方式就會產(chǎn)生性能問題骑疆。這種情況下利用事件委托就能很好的解決這個問題。
改用事件委托:
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>green</li>
<li>black</li>
<li>white</li>
</ul>
<script>
(function () {
var color_list = document.getElementByid('color-list');
color_list.addEventListener('click', showColor, true);
function showColor(e) {
var x = e.target;
if (x.nodeName.toLowerCase() === 'li') {
alert(x.innerHTML);
}
}
})();
</script>
二替废、渲染完成后的頁面交互優(yōu)化:
防抖(debounce)/節(jié)流(throttle)
防抖(debounce)
輸入搜索時箍铭,可以用防抖debounce等優(yōu)化方式,減少http請求椎镣;
這里以滾動條事件舉例:防抖函數(shù) onscroll 結束時觸發(fā)一次诈火,延遲執(zhí)行
function debounce(func, wait) {
let timeout;
return function() {
let context = this; // 指向全局
let args = arguments;
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
func.apply(context状答, args); // context.func(args)
}土铺, wait);
};
}
// 使用
window.onscroll = debounce(function() {
console.log('debounce');
}川陆, 1000);
節(jié)流(throttle)
節(jié)流函數(shù):只允許一個函數(shù)在N秒內(nèi)執(zhí)行一次。滾動條調(diào)用接口時,可以用節(jié)流throttle等優(yōu)化方式理张,減少http請求忿族;
下面還是一個簡單的滾動條事件節(jié)流函數(shù):節(jié)流函數(shù) onscroll 時抖锥,每隔一段時間觸發(fā)一次硼砰,像水滴一樣
function throttle(fn, delay) {
let prevTime = Date.now();
return function() {
let curTime = Date.now();
if (curTime - prevTime > delay) {
fn.apply(this蜡娶, arguments);
prevTime = curTime;
}
};
}
// 使用
var throtteScroll = throttle(function() {
console.log('throtte');
}混卵, 1000);
window.onscroll = throtteScroll;
三、Vue相關性能優(yōu)化
如何定位 Vue 應用性能問題
Vue 應用的性能問題可以分為兩個部分翎蹈,第一部分是運行時性能問題淮菠,第二部分是加載性能問題。
和其他 web 應用一樣荤堪,定位 Vue 應用性能問題最好的工具是 Chrome Devtool合陵,通過 Performance 工具可以用來錄制一段時間的 CPU 占用枢赔、內(nèi)存占用、FPS 等運行時性能問題拥知,通過 Network 工具可以用來分析加載性能問題踏拜。
更多 Chrome Devtool 使用方式請參考 使用 Chrome Devtool 定位性能問題 的指南
Vue 應用運行時性能優(yōu)化建議
運行時性能主要關注 Vue 應用初始化之后對 CPU、內(nèi)存低剔、本地存儲等資源的占用速梗,以及對用戶交互的及時響應。
引入生產(chǎn)環(huán)境的 Vue 文件
開發(fā)環(huán)境下襟齿,Vue 會提供很多警告來幫你對付常見的錯誤與陷阱姻锁。而在生產(chǎn)環(huán)境下,這些警告語句沒有用猜欺,反而會增加應用的體積位隶。有些警告檢查還有一些小的運行時開銷。
當使用 webpack 或 Browserify 類似的構建工具時开皿,Vue 源碼會根據(jù) process.env.NODE_ENV 決定是否啟用生產(chǎn)環(huán)境模式涧黄,默認情況為開發(fā)環(huán)境模式。在 webpack 與 Browserify 中都有方法來覆蓋此變量赋荆,以啟用 Vue 的生產(chǎn)環(huán)境模式笋妥,同時在構建過程中警告語句也會被壓縮工具去除。
詳細的做法請參閱 生產(chǎn)環(huán)境部署
使用單文件組件預編譯模板
當使用 DOM 內(nèi)模板或 JavaScript 內(nèi)的字符串模板時窄潭,模板會在運行時被編譯為渲染函數(shù)春宣。通常情況下這個過程已經(jīng)足夠快了,但對性能敏感的應用還是最好避免這種用法狈孔。
預編譯模板最簡單的方式就是使用單文件組件——相關的構建設置會自動把預編譯處理好信认,所以構建好的代碼已經(jīng)包含了編譯出來的渲染函數(shù)而不是原始的模板字符串材义。
詳細的做法請參閱 預編譯模板
提取組件的 CSS 到單獨到文件
當使用單文件組件時均抽,組件內(nèi)的 CSS 會以 <style> 標簽的方式通過 JavaScript 動態(tài)注入。這有一些小小的運行時開銷其掂,將所有組件的 CSS 提取到同一個文件可以避免這個問題油挥,也會讓 CSS 更好地進行壓縮和緩存。
查閱這個構建工具各自的文檔來了解更多:
- webpack + vue-loader (vue-cli 的 webpack 模板已經(jīng)預先配置好)
- Browserify + vueify
- Rollup + rollup-plugin-vue
利用Object.freeze()提升性能
Object.freeze() 可以凍結一個對象款熬,凍結之后不能向這個對象添加新的屬性深寥,不能修改其已有屬性的值,不能刪除已有屬性贤牛,以及不能修改該對象已有屬性的可枚舉性惋鹅、可配置性、可寫性殉簸。該方法返回被凍結的對象闰集。
當你把一個普通的 JavaScript 對象傳給 Vue 實例的 data 選項沽讹,Vue 將遍歷此對象所有的屬性,并使用Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter武鲁,這些 getter/setter 對用戶來說是不可見的爽雄,但是在內(nèi)部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化沐鼠。
但 Vue 在遇到像 Object.freeze() 這樣被設置為不可配置之后的對象屬性時挚瘟,不會為對象加上 setter getter 等數(shù)據(jù)劫持的方法。 參考 Vue 源碼
Object.freeze()### 應用場景
由于 Object.freeze() 會把對象凍結饲梭,所以比較適合展示類的場景乘盖,如果你的數(shù)據(jù)屬性需要改變,可以重新替換成一個新的 Object.freeze()的對象憔涉。
扁平化 Store 數(shù)據(jù)結構
很多時候侧漓,我們會發(fā)現(xiàn)接口返回的信息是如下的深層嵌套的樹形結構:
{
"id": "123",
"author": {
"id": "1"监氢,
"name": "Paul"
}布蔗,
"title": "My awesome blog post",
"comments": [
{
"id": "324"浪腐,
"commenter": {
"id": "2"纵揍,
"name": "Nicole"
}
}
]
}
假如直接把這樣的結構存儲在 store 中,如果想修改某個 commenter 的信息议街,我們需要一層層去遍歷找到這個用戶的信息泽谨,同時有可能這個用戶的信息出現(xiàn)了多次,還需要把其他地方的用戶信息也進行修改特漩,每次遍歷的過程會帶來額外的性能開銷吧雹。
假設我們把用戶信息在 store 內(nèi)統(tǒng)一存放成 users[id]這樣的結構,修改和讀取用戶信息的成本就變得非常低涂身。
你可以手動去把接口里的信息通過類似數(shù)據(jù)的表一樣像這樣存起來雄卷,也可以借助一些工具,這里就需要提到一個概念叫做 JSON數(shù)據(jù)規(guī)范化(normalize)蛤售, Normalizr 是一個開源的工具丁鹉,可以將上面的深層嵌套的 JSON 對象通過定義好的 schema 轉(zhuǎn)變成使用 id 作為字典的實體表示的對象。
避免持久化 Store 數(shù)據(jù)帶來的性能問題
當你有讓 Vue App 離線可用悴能,或者有接口出錯時候進行災備的需求的時候揣钦,你可能會選擇把 Store 數(shù)據(jù)進行持久化,這個時候需要注意以下幾個方面:
- 持久化時寫入數(shù)據(jù)的性能問題
Vue 社區(qū)中比較流行的 vuex-persistedstate漠酿,利用了 store 的 subscribe 機制冯凹,來訂閱 Store 數(shù)據(jù)的 mutation,如果發(fā)生了變化炒嘲,就會寫入 storage 中宇姚,默認用的是 localstorage 作為持久化存儲团驱。
也就是說默認情況下每次 commit 都會向 localstorage 寫入數(shù)據(jù),localstorage 寫入是同步的空凸,而且存在不小的性能開銷嚎花,如果你想打造 60fps 的應用,就必須避免頻繁寫入持久化數(shù)據(jù)呀洲。
我們應該盡量減少直接寫入 Storage 的頻率:
* 多次寫入操作合并為一次紊选,比如采用函數(shù)節(jié)流或者將數(shù)據(jù)先緩存在內(nèi)存中,最后在一并寫入
* 只有在必要的時候才寫入道逗,比如只有關心的模塊的數(shù)據(jù)發(fā)生變化的時候才寫入
- 避免持久化存儲的容量持續(xù)增長
由于持久化緩存的容量有限兵罢,比如 localstorage 的緩存在某些瀏覽器只有 5M,我們不能無限制的將所有數(shù)據(jù)都存起來滓窍,這樣很容易達到容量限制卖词,同時數(shù)據(jù)過大時,讀取和寫入操作會增加一些性能開銷吏夯,同時內(nèi)存也會上漲此蜈。
尤其是將 API 數(shù)據(jù)進行 normalize 數(shù)據(jù)扁平化后之后,會將一份數(shù)據(jù)散落在不同的實體上噪生,下次請求到新的數(shù)據(jù)也會散落在其他不同的實體上裆赵,這樣會帶來持續(xù)的存儲增長。
因此跺嗽,當設計了一套持久化的數(shù)據(jù)緩存策略的時候战授,同時應該設計舊數(shù)據(jù)的緩存清除策略,例如請求到新數(shù)據(jù)的時候?qū)⑴f的實體逐個進行清除桨嫁。
優(yōu)化無限列表性能
如果你的應用存在非常長或者無限滾動的列表植兰,那么采用 窗口化 的技術來優(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)化性能肚逸,主要使用到的技術是 DOM 回收爷辙、墓碑元素和滾動錨定。
Google 工程師繪制的無限列表設計
通過組件懶加載優(yōu)化超長應用內(nèi)容初始渲染性能
上面提到的無限列表的場景朦促,比較適合列表內(nèi)元素非常相似的情況,不過有時候栓始,你的 Vue 應用的超長列表內(nèi)的內(nèi)容往往不盡相同务冕,例如在一個復雜的應用的主界面中,整個主界面由非常多不同的模塊組成幻赚,而用戶看到的往往只有首屏一兩個模塊禀忆。在初始渲染的時候不可見區(qū)域的模塊也會執(zhí)行和渲染臊旭,帶來一些額外的性能開銷。
使用組件懶加載在不可見時只需要渲染一個骨架屏箩退,不需要真正渲染組件
你可以對組件直接進行懶加載离熏,對于不可見區(qū)域的組件內(nèi)容,直接不進行加載和初始化戴涝,避免初始化渲染運行時的開銷滋戳。具體可以參考我們之前的專欄文章 性能優(yōu)化之組件懶加載: Vue Lazy Component 介紹,了解如何做到組件粒度的懶加載啥刻。
Vue 應用加載性能優(yōu)化建議
利用服務端渲染(SSR)和預渲染(Prerender)來優(yōu)化加載性能
在一個單頁應用中奸鸯,往往只有一個 html 文件,然后根據(jù)訪問的 url 來匹配對應的路由腳本可帽,動態(tài)地渲染頁面內(nèi)容娄涩。單頁應用比較大的問題是首屏可見時間過長。
單頁面應用顯示一個頁面會發(fā)送多次請求映跟,第一次拿到 html 資源蓄拣,然后通過請求再去拿數(shù)據(jù),再將數(shù)據(jù)渲染到頁面上努隙。而且由于現(xiàn)在微服務架構的存在弯蚜,還有可能發(fā)出多次數(shù)據(jù)請求才能將網(wǎng)頁渲染出來,每次數(shù)據(jù)請求都會產(chǎn)生 RTT(往返時延)剃法,會導致加載頁面的時間拖的很長碎捺。
服務端渲染、預渲染和客戶端渲染的對比
這種情況下可以采用服務端渲染(SSR)和預渲染(Prerender)來提升加載性能贷洲,這兩種方案收厨,用戶讀取到的直接就是網(wǎng)頁內(nèi)容,由于少了節(jié)省了很多 RTT(往返時延)优构,同時诵叁,還可以對一些資源內(nèi)聯(lián)在頁面,可以進一步提升加載的性能钦椭。
可以參考專欄文章 優(yōu)化向:單頁應用多路由預渲染指南 了解如何利用預渲染進行優(yōu)化拧额。
服務端渲染(SSR)可以考慮使用 Nuxt 或者按照 Vue 官方提供的 Vue SSR 指南 來一步步搭建。
通過組件懶加載優(yōu)化超長應用內(nèi)容加載性能
在上面提到的超長應用內(nèi)容的場景中彪腔,通過組件懶加載方案可以優(yōu)化初始渲染的運行性能侥锦,其實,這對于優(yōu)化應用的加載性能也很有幫助德挣。
組件粒度的懶加載結合異步組件和 webpack 代碼分片恭垦,可以保證按需加載組件,以及組件依賴的資源、接口請求等番挺,比起通常單純的對圖片進行懶加載唠帝,更進一步的做到了按需加載資源。
使用組件懶加載之前的請求瀑布圖
使用組件懶加載之后的請求瀑布圖
使用組件懶加載方案對于超長內(nèi)容的應用初始化渲染很有幫助玄柏,可以減少大量必要的資源請求襟衰,縮短渲染關鍵路徑,具體做法請參考我們之前的專欄文章 性能優(yōu)化之組件懶加載: Vue Lazy Component 介紹 粪摘。
總結
上面部分總結了 Vue 應用運行時以及加載時的一些性能優(yōu)化措施瀑晒,下面做一個回顧和概括:
Vue 應用運行時性能優(yōu)化措施
引入生產(chǎn)環(huán)境的 Vue 文件
使用單文件組件預編譯模板
提取組件的 CSS 到單獨到文件
利用Object.freeze()提升性能
扁平化 Store 數(shù)據(jù)結構
合理使用持久化 Store 數(shù)據(jù)
組件懶加載
Vue 應用加載性能優(yōu)化措施
服務端渲染 / 預渲染
組件懶加載
文章總結的這些性能優(yōu)化手段當然不能覆蓋所有的 Vue 應用性能問題,我們也會不斷總結和補充其他問題及優(yōu)化措施赶熟,希望文章中提到這些實踐經(jīng)驗能給你的 Vue 應用性能優(yōu)化工作帶來小小的幫助瑰妄。
四、其他方面優(yōu)化補充
- webpack模塊打包和JavaScript 壓縮(如gzip壓縮)
- 利用CDN
- 按需加載資源
- 在使用 DOM 操作庫時用上 array-ids
- 緩存優(yōu)化
- 避免重定向
- 啟用 HTTP/2
- 應用性能分析
- 使用負載均衡方案
- 為了更快的啟動時間考慮一下同構
- 使用索引加速數(shù)據(jù)庫查詢
- 使用更快的轉(zhuǎn)譯方案
- 避免或最小化 JavaScript 和 CSS 的使用而阻塞渲染
- 用于未來的一個建議:使用 service workers + 流
- 圖片編碼優(yōu)化映砖,盡量使用svg和字體圖標
原作者姓名:云魚Cloudy
原出處:segmentfault
原文鏈接:前端性能優(yōu)化小結(面試干貨) - JavaScript進階之路 - SegmentFault 思否