1. 程式人生 > 其它 >vue 效能優化

vue 效能優化

開場白: 個人認為效能優化可以從三個方面來進行:

  • 程式碼層面的優化

  • 專案大包的優化

  • 專案部署的優化

1. 優化迴圈

大多數迴圈使用一個從0開始、增加到某個特定的迭代器。在很多情況下,從最大值開始在迴圈中不斷減值的迭代器更加高效。使用後測試迴圈——最常用for迴圈和while迴圈都是前測試迴圈,do-while後測試迴圈,可以避免最初終止條件的計算。

for(var i = 0; i < values.length; i++){
process(values[i]);
}
複製程式碼

可改為減值,這個過程中將終止條件從value.lengthO(n)呼叫簡化成了0O(1)

呼叫。

for(var i = values.length - 1; i >= 0; i--){
process(values[i]);
}
複製程式碼

後測試迴圈主要的是將終止條件和自減操作符組合成了單個語句。

var i = values.length - 1;
if ( i > -1) {
do {
process(value[i]);
} while(--i >= 0);
}
複製程式碼

請記住:使用“後測試迴圈”時,必須確保要處理的值至少有一個。空陣列會導致多餘的一次迴圈,而“前測試迴圈”則可以避免

2. 陣列分塊

應用場景:如果處理的專案在執行前不可知,完成process()

需要100ms,只有2個專案的陣列可能不會造成什麼影響,但10個的陣列可能會導致指令碼要運營一秒鐘才能完成。如果這些處理不必須同步完成;資料也不必須按順序完成,可使用定時器分割這個迴圈,實現 “陣列分塊”

// @array->要處理的陣列;@process->用於處理專案的函式;@context->可選的執行該函式的環境
function chunk (array, process, context) {
setTimeout(function() {
// 取出下一個條目並處理
var item = array.shift();
process.call(context, item);
// 若還有條目,再設定另一個定時器
if (array.length > 0) {
setTimeout(arguments.callee, 100);
}
})
}
複製程式碼

需要擔心的地方是,傳遞給chunk()的陣列是用作一個佇列的,因此當處理資料的同時,陣列中的條目也在改變,如果想保持原陣列不變,可將該陣列克隆傳給chunk(),當不傳遞任何引數呼叫某個陣列的concat()方法時,將返回和原來陣列中專案一樣的陣列。如:

chunk(arr.concat(), fun1);
複製程式碼

一旦某個函式需要花費50ms以上的時間完成,那麼最好看看能否將任務分割為一系列可以使用定時器的小任務。

3. switch語法較快

如果有一系列複雜的if-else語句,可以轉換成單個switch語句可以得到更快的程式碼。

4. 最小化語句數

4.1 多個變數宣告

這種優化非常容易做,而且要比單個變數分別宣告快

// 不推薦
var count = 5;
var color = 'blue';
var values = [1,2,3];

// 推薦
var count = 5,
color = 'blue',
values = [1,2,3];
複製程式碼

4.2 插入迭代值

當使用迭代值(也就是在不同位置進行增加或減少的值)的時候,儘可能合併語句。

// 不推薦
var name = values[i];
i++;

// 推薦
var name = values[i++];
複製程式碼

4.3 使用陣列和物件字面量

減少語句量

// 不推薦
var arr = new Array();
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
var obj = new Object();
obj.id = '';
obj.name = '';

// 推薦
var arr = [1,2,3];
var obj = {
id = '',
name = ''
}
複製程式碼

5. 利用v-if和v-show減少初始化渲染和切換渲染的效能開銷

在頁面載入時,v-if更適合新增不經常改變的場景,因為它切換開銷相對比較大,而v-show適用於頻繁切換條件。 原理: v-if 繫結值為false時,初始渲染時,不會渲染其條件塊。 v-if 繫結值,在true和false之間切換時,會銷燬和重新渲染其條件塊。 v-show 繫結值不管為true還是為false,初始渲染時,總是會渲染其條件塊。 v-show 繫結值,在true和false之間切換時,不會銷燬和重新渲染其條件塊,只是用display:none樣式來控制其顯示隱藏。

6. computed、watch、methods區分使用場景

對於有些需求,computed、watch、methods都可以實現,但是還是要區分一下使用場景。用錯場景雖然功能實現了但是影響了效能。

  • computed: 一個數據受多個數據影響的。

該資料要經過效能開銷比較大的計算,如它需要遍歷一個巨大的陣列並做大量的計算才能得到,原因就是計算屬性是基於它的依賴快取,只有它計算時依賴的資料發現變化時才會重新計算,否則直接返回快取值。

  • methods: 希望資料是實時更新,不需要快取。

  • watch: 一個數據影響多個數據的。

當資料變化時,需要執行非同步或開銷較大的操作時。如果資料變化時請求一個介面。 如果深度監聽一個物件中的某一個屬性,不用deep:true效能更好。

7. 提前處理好資料解決v-if和v-for必須同級的問題

因為當Vue處理指令時,v-forv-if具有更高的優先順序,意味著v-if將分別重複運行於每個v-for迴圈中。 可以在computed中提前把要v-for的資料中v-if的資料項給過濾處理了。

<!-- 不推薦 -->
<template>
<div>
<div v-for="item in userList"
:key="item.id"
v-if="item.age > 18">
{{ item.name }}
</div>
</div>
</template>

<!-- 推薦 -->
<template>
<div>
<div v-for="item in userComputedList"
:key="item.id">
{{ item.name }}
</div>
</div>
</template>

export default{
computed:{
userComputedList:function(){
return this.userList.filter(item => {
return item.age > 18
})
}
}
}
複製程式碼

8. 給v-for迴圈項加上key提高diff計算速度

  • 為什麼加key會提高diff計算速度。

經過舊頭新頭、舊尾新尾、舊頭新尾、舊尾新頭四次交叉比對後,都沒有匹配到值得比對的節點,這時如果新節點有key的話。可以通過map直接獲得值得對比的舊節點的下標,如果沒有key的話,就要通過迴圈舊節點陣列用sameVnode方法判斷新節點和該舊節點是否值得比較,值得就返回該舊節點的下標。顯然通過map比通過迴圈陣列的計算速度來的快。

  • 什麼是diff計算。

對於渲染watcher觸發時會執行vm.update(vm.render(), hydrating),在vm.undata方法中會呼叫vm._patch,而vm.__patch指向patch方法,diff計算是指在呼叫patch方法開始,用sameVnode方法判斷節點是否值得比較,若不值得直接新節點替換舊節點後結束。值得對比進入patchVnode方法,分別處理一下幾種情況,若新舊節點都有文字節點,新節點下的文字節點直接替換舊節點下的文字節點,如果新節點有子節點,舊節點沒有子節點,那麼直接把新節點查到舊節點的父級中,如果新節點沒有子節點,舊節點有子節點,那麼舊節點的父級下的子節點都刪了。如果新舊節點都有子節點,進入updateChildren方法,通過舊頭新頭、舊尾新尾、舊頭新尾、舊尾新頭四次交叉比對,如果值得對比再進入patchVnode方法,如果都不值得對比,有key用map獲得值得對比的舊節點,沒有key通過迴圈舊節點獲得值得對比的舊節點。當新節點都對比完,舊節點還沒對比完,將還沒對比完的舊節點刪掉。當舊節點都對比完,新節點還沒對比完,將新節點新增到最後一個對比過的新節點後面,完成diff計算。 :key的選擇不推薦使用index會導致不好的快取

9. 利用v-once處理只會渲染一次的元素或元件

只渲染元素和元件一次。隨後的重新渲染,元素/元件及其所有的子節點將被視為靜態內容並跳過。這可以用於優化更新效能。 例如某個頁面是合同範文,裡面大部分內容從服務端獲取且是固定不變,只有姓名、產品、金額等內容會變動。這時就可以把v-once新增到那些包裹固定內容的元素上,當生成新的合同可以跳過那些固定內容,只重新渲染姓名、產品、金額等內容即可。 和v-if一起使用時,v-once不生效。在v-for迴圈內的元素或元件上使用,必須加上key

10. 利用Object.freeze()凍結不需要響應式變化的資料

Vue初始化過程中,會把data傳入observe函式中進行資料劫持,把data中的資料都轉換成響應式的。 在observe函式內部呼叫defineReactive函式處理資料,配置getter/setter屬性,轉成響應式,如果使用Object.freeze()將data中某些資料凍結了,也就是將其configurable屬性(可配置)設定為false。 defineReactive函式中有段程式碼,檢測資料上某個key對應的值的configurable屬性是否為false,若是就直接返回,若不是繼續配置getter/setter屬性。

// 凍結後的物件不會被修改,不能對這個物件進行新增新增屬性,不能刪除已有屬性,不能修改該物件已有屬性的可列舉性,可配置性,可寫性。
const obj1={
age: 18
};
const obj2 = Object.freeze(obj1); // 凍結這個物件
obj2.age = 20;
console.log(obj2.age); // 18
複製程式碼

在專案中如果遇到不需要響應式變化的資料,可以用Object.freeze()把該資料凍結了,可以跳過初始化時資料劫持的步驟,大大提高初次渲染速度。 這時你可能想到用const 看一個例子你就會發現不同:

const a = {name:'11'}
a.name = '22';
console.log(a); // {name: '22'}
複製程式碼

11. 提前過濾掉非必須資料,優化data選項中的資料結構

Vue初始化時,會將選項data傳入observe函式中進行資料劫持。 接收服務端傳來的資料,都會有一些渲染頁面時用不到的資料。服務端的慣例,寧可多傳也不會少傳。 所以要先把服務端傳來的資料中那些渲染頁面用不到的資料先過濾掉。然後再賦值到data選項中。可以避免去劫持那些非渲染頁面需要的資料,減少迴圈和遞迴呼叫,從而提高渲染速度。

如果不需要觀測某個變數的變化,可以不用在data中進行宣告

12. 避免在v-for迴圈中讀取data中陣列型別的資料

舉個簡單的栗子,表格中每行有兩個輸入框,分別可以輸入駕駛員和電話,程式碼這麼實現。

<template>
<div>
<el-table :data="tableData">
<el-table-column prop="carno" label="車牌號"></el-table-column>
<el-table-column prop="cartype" label="車型"></el-table-column>
<el-table-column label="駕駛員">
<template slot-scope="{row, column, $index}">
<el-input v-model="driverList[$index].phone"></el-input>
</template>
</el-table-column>
</el-table>
</div>
</template>
複製程式碼

假設表格有500條資料,那麼讀取driverList共500次,每次都讀取driverList都會進入dependArray(value)中,總共要迴圈500*500=25萬次,若有分頁,每次切換頁碼,都會至少迴圈25萬次。 如果我們在從服務獲取到資料後,做了如下預處理,在賦值給this.tableData,會是怎麼樣?

res.data.forEach(item => {
item.name='';
item.phone='';
})

<!-- 模板這樣實現 -->
<template>
<div>
<el-table :data="tableData">
<el-table-column prop="carno" label="車牌號"></el-table-column>
<el-table-column prop="cartype" label="車型"></el-table-column>
<el-table-column label="駕駛員">
<template slot-scope="{row}">
<el-input v-model="row.phone"></el-input>
</template>
</el-table-column>
</el-table>
</div>
</template>
複製程式碼

也可以實現需求,渲染過程中求值時也不會進入dependArray(value)中,也不會造成25萬次的不必要的迴圈。大大提高了效能。

13. 防抖和節流

節流是一定時間內執行一次函式,多用在scroll事件上; 防抖是在一定時間內執行最後一次函式,多用在input輸入、提交上。

14. 圖片大小優化和懶載入

關於圖片大小的優化,可以用image-webpack-loader進行壓縮圖片,在webpack外掛中配置,具體可以看本文中這點。 關於圖片懶載入,可以用vue-lazyload外掛實現。 執行命令npm install vue-lazyload —save安裝vue-lazyload外掛。在main.js中引入配置。

import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload, {
preLoad: 1.3, // 預載高度比例
error:'dist/error.png', // 載入失敗顯示圖片
loading: 'dist/loading.git', // 載入過程中顯示圖片
attempt: 1 // 嘗試次數
})

// 在專案中使用
<img v-lazy="/static/img/1.png">
複製程式碼

15. 利用掛在節點會被替換的特性優化白屏問題

import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h =>h(App)
}).$mount('#app)
複製程式碼

Vue 選項中的 render 函式若存在,則 Vue 建構函式不會從 template 選項或通過 el 選項指定的掛載元素中提取出的 HTML 模板編譯渲染函式。 也就是說渲染時,會直接用render渲染出來的內容替換<div id="app"></div> Vue專案有個缺點,首次渲染會有一段時間的白屏原因是首次渲染時需要載入一堆資源,如js、css、圖片。很多優化策略,最終目的是提高這些資源的載入速度。但是如果遇上網路慢的情況,無論優化到極致還是需要一定載入時間,這時就會出現白屏現象。 首先載入是index.html頁面,其是沒有內容,就會出現白屏。如果<div id="app"></div>裡面有內容,就不會出現白屏。所以我們可以在<div id="app"></div>裡新增首屏的靜態頁面。等真正的首屏加載出來後就會把<div id="app"></div>這塊結構都替換掉,給人一種視覺上的誤差,就不會產生白屏。 vue中首頁白屏優化

16. 初始化頁面閃動

webpack,vue-router v-cloak css:[v-cloak]:display:none

17. 元件庫的按需引入

元件庫按需引入的方法,一般文件都會介紹。 如element UI庫,用babel-plugin-component外掛實現按需引入。 執行命令npm install babel-plugin-component —save-dev,安裝外掛。 在根目錄下.babelrc.js檔案中按如下配置。

{
"presets":[["es2015", {"modules":false}]],
"plugins":[
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
複製程式碼

其中libraryName為元件庫的名稱,styleLibraryName為元件庫打包後樣式存放的資料夾名稱。 在main.js中就可以按需引入。

import Vue from 'vue';
import {Button, Select} from 'element-ui';
Vue.use(Button);
Vue.use(Select);
複製程式碼

其實babel-plugin-component外掛是element用babel-plugin-import外掛改造後特定給element UI使用。一般的元件庫還是babel-plugin-import外掛實現按需引入。 執行命令npm install babel-plugin-import —save-dev,安裝外掛。 在根目錄下.babelrc.js檔案中按如下配置。

{
"plugins":[
["import", {
"libraryName": "vant",
"libraryDiectory": "es",
"style": true
}]
]
}
複製程式碼

其中libraryName為元件庫的名稱,libraryDirectory表示從庫的package.json的main入口檔案或者module入口檔案所在資料夾名稱,否則預設為lib。 在介紹style選項配置之前。先看一下Vant 元件庫打包後生成檔案的結構和內容。 style為true時,會按需在專案中引入對應style檔案中的index.js。 style為css時,會按需在專案中引入對應style檔案中的less.js。 style為Function,babel-plugin-import將自動匯入檔案路徑等於函式返回值的檔案。