咱們公司的內部系統中,有一個配置組件(如圖所示),功能爲實現左邊勾選節點,右邊顯示所選節點的交互列表(這裏和下面都作了字段脫敏處理,因此看起來沒什麼意義,但功能是同樣的)。node
上線後,一直風平浪靜,可是有一天,測試數據庫被導入了一批1.8萬的數據,而後致使這個組件的所在頁面出現了下面的性能問題。git
performance
標籤(下圖所示),備用;這裏錄製了列表點擊全選時的時間段(以下圖所示)。首先這裏作個說明:performance
能夠分析內存、CPU、FPS等好多參數,咱們這裏主要是頁面等待過久了,而這個取決於FPS
(頁面每秒幀數,fps < 24 會讓用戶感受到卡頓,由於人眼的識別主要是24幀)。github
咱們按着圖上序號順序解釋:chrome
FPS
區域出現了紅條,意味着幀數已經降低到影響用戶體驗的程度,chrome已經幫你標註了這塊是有問題;Main
區域展現的是火焰圖,也就是函數調用的堆棧(火焰圖,能夠簡單理解,x軸表示時間,y軸表示調用的函數,函數中還包含依次調用的函數,y軸只佔用x軸的一個時間維度),點擊灰色條會出現圖中3的紅色區域。Summary
區域顯示了Warning
,長時間任務耗時了1.6min
,其中腳本Scripting
佔絕大部分耗時。這時,咱們已經知道有一個耗時很長的任務了,咱們繼續深究究竟是哪一些代碼形成的:數據庫
經過Chrome,咱們已經定位出問題代碼了,接下來就是分析問題的緣由了。數組
這裏列出有性能問題的代碼塊:bash
...
computed: {
treeData () {
const tree = this.formmatData(this.data);
return tree;
},
...
},
methods: {
formmatData (origin, rootId = 0, result = {}) {
// 1.找出根節點數組
const rootNode = origin.filter(node => node.fatherModuleId === rootId);
if (rootNode.length > 0) {
// 2.對根節點數組進行遍歷賦值,並放到傳入result的children中
result.children = rootNode.map(node => ({
title: node.moduleName,
id: node.moduleId,
checked: origin.filter(n => n.fatherModuleId === node.moduleId).length < 1 && this.checked.includes(node.moduleId),
expand: true,
disableCheckbox: !this.editable || this.disabledList.includes(node.moduleId),
}));
//3. 遍歷根節點數組,遞歸調用該方法找下一級的節點
result.children.forEach((child) => {
this.formmatData(origin, child.id, child);
});
}
// 4.返回當前的result的孩子
return result.children;
},
...
}
複製代碼
其中this.data
數據格式爲:函數
[
{
"moduleId": 1,
"moduleName": "節點1",
"fatherModuleId": 0
},
{
"moduleId": 2,
"moduleName": "節點2",
"fatherModuleId": 0
},
{
"moduleId": 3,
"moduleName": "節點3",
"fatherModuleId": 0
},
...
]
複製代碼
這裏先說明一下,該段代碼作了什麼事情吧:post
data
包含三個字段moduleId
、fatherModuleId
和moduleName
,其中moduleId
是模塊ID,fatherModuleId
是模塊上一級的ID。fromData
方法計算出treeData
,而formmatData
作的就是遞歸調用,構造出樹狀數據。可是,就是由於裏面出現了遞歸,循環的嵌套致使了性能問題,經過結合業務,優化作了如下操做:性能
set
來優化查找優化後的代碼:
computed: {
checkedSet () {
return new Set(this.checked);
},
...
}
methods: {
formmatData (origin, rootId = 0, result = {}) {
const otherNode = [];
result.children = [];
// 1. 整合本來代碼的過濾和遍歷賦值,減小一次循環
for (const node of origin) {
if (node.fatherModuleId === rootId) {
result.children.push({
title: node.moduleName,
id: node.moduleId,
checked: this.checkedSet.has(node.moduleId), // 利用`set`來優化查找
expand: true,
disableCheckbox: !this.editable || this.disabledList.includes(node.moduleId),
});
} else {
otherNode.push(node);
}
}
// 2.在剩下的節點去查找子節點,而不是重複在所有數據中查找,由於當前的節點數組已經找到
if (otherNode.length > 0) {
result.children.forEach((child) => {
if (child.checked) {
// 3.利用find來代替filter
child.checked = !otherNode.find(n => n.fatherModuleId === child.id);
}
this.formmatData(otherNode, child.id, child);
});
}
return result.children;
},
...
}
複製代碼
通過一番定位耗時問題,優化代碼邏輯後,先後時間對好比下:
Chrome Performance
是個很好的定位問題利器,方便快速找到問題代碼;set
在查找方面,數據量很大時性能大幅高於數組,推薦使用。