全篇主要思想:遞歸的本質是棧的讀取
先看效果對比
以下都是基於10000條子節點數據作對比,先上最終數據對比:
遞歸版tree,渲染速度: 14.65s,點擊節點處理速度: 9.83s
優化版tree,渲染速度: 0.49s,點擊節點處理速度: 0.18s
遞歸組件實現tree:渲染速度 15.71s -1.06s = 14.65s
遞歸組件版tree點擊節點性能分析圖:點擊節點處理速度: 10.19s - 0.357s = 9.833s ≈ 9.83s
最終優化實現tree: 渲染速度2.25s - 1.76s = 0.49s
優化版tree點擊節點性能分析圖:點擊節點處理速度0.623s - 0.3s = 0.3s
最終對比是:
遞歸版tree,渲染速度: 12.19s,點擊節點處理速度: 9.52s
優化版tree,渲染速度: 0.49s,點擊節點處理速度: 0.18s
分析問題
我們可以藉助performance分析一下遞歸組件的耗時點所在,上遞歸組件渲染的性能分析:
1.script耗時分析:
通過圖-1性能瀑布可以清晰的看到script執行佔了8.9s的時間,通過上圖即圖-5可以看到script的的調用棧主要集中在創建vue實例時的createChildren上面。
2.render耗時分析
通過上圖即圖-6可以清晰的看到render耗時主要集中在Recalculate Style、Layout上面。我們知道Recalculate Style、Layout主要是樣式計算,因此查看代碼:
發現在遞歸的tree-node組件裏面有很多樣式的計算,10000條子節點就需要計算10000次。
3.DOM結構分析
實現思想
來看我們的開篇思想:遞歸的本質是棧的讀取。
在算法中我們會遇到很多遞歸實現的案例,所有的遞歸都可以轉換成非遞歸實現,其中轉換的本質是:遞歸是解析器(引擎)來幫我們做了棧的存取,非遞歸是手動創建棧來模擬棧的存取過程。
萬物都是相通的,遞歸組件也可以轉換成扁平數組來實現:
1.更改DOM結構成平級結構,點擊節點以及節點的視覺樣式通過操作總的list數據去實現
2.然後使用虛擬長列表來控制vue子組件實例創建的數量。
優化版實現
主要分爲兩部分功能:
1.tree數據和DOM結構的扁平化;
2.虛擬長列表控制DOM渲染數量。
1.tree數據和DOM結構的扁平化
由上圖我們可以看到經過改造之後的tree的DOM結構,父節點和子節點是平級的,在操作子節點時去操作內存中的listData數據來改變相關聯節點的狀態。
我們再看下listData數據的結構:
上圖即圖-10結合圖-9的DOM結構可以對整個功能的實現邏輯一目瞭然:
listData中的每一項的style、checked、path等信息來描述節點的樣式位置和狀態,操作一個節點時通過listData更改相關節點的狀態樣式等信息。
因此最終來寫我們的代碼:
我們再看下handleCheckChange的做了什麼:
2.虛擬長列表控制DOM渲染數量
實現思路:
根節點DOM分成兩個子節點:fui-tree__phantom 和 fui-tree__content。
兩個子節點都是絕對定位,爲了在滾動時避免數據的更改回頭觸發滾動事件。
根節點解兩個子節點css:
.fui-tree {
height: 400px;
overflow: auto;
position: relative;
}
.fui-tree__phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.fui-tree__content {
left: 0;
right: 0;
top: 0;
position: absolute;
}
然後我們通過滾動條的位置來計算我們應該要取哪些數據。
主要代碼:
計算可視區數據的起止索引index圖-14通過startIndex、endIndex可以取出我們需要循環的數據列表renderNodes:
computed: {
renderNodes() {
if (!this.treeNode) return []
return this.treeNode.listData.slice(this.positionConfig.startIndex, this.positionConfig.endIndex)
},
phantomStyle() {
return {
height: this.allListLen * 20 + 'px'
}
}
}
結合圖-11的v-for,這樣我們在渲染時的dom數量是固定的條數,如下圖:
虛擬列表的接入可以讓即使再多數據量也能渲染固定的DOM數量,這樣就可以支撐更大數據的渲染和功能。
以上我們實現了業務需求的大數據渲染,目前測試可支撐到20w條節點,點擊子節點時會有肉眼可見的延遲,主要是圖-12中handleCheckChange的數據查找和處理,這塊還有一定的優化空間:使用字典樹存儲節點相關信息,字典樹和扁平數組listData的每一個元素指向同一個內存地址,在handleCheckChange中通過操作字典樹來達到操作listData的元素的效果,經典的空間換時間的案例。
參考鏈接
更多內容,請關注前端之巔。