遞歸的本質是棧的讀取
如下都是基於10000條子節點數據做對比
css
遞歸組件實現tree:渲染速度 13.75s -1.56s = 12.19s遞歸版tree,渲染速度: 12.19s,點擊節點處理速度: 9.52s
優化版tree,渲染速度: 0.49s,點擊節點處理速度: 0.18s
vue
遞歸組件版tree性能圖-1node
遞歸組件版tree點擊節點性能分析圖:點擊節點處理速度: 9.64s - 0.117s = 9.523s ≈ 9.52s算法
優化版tree: 渲染速度2.54s - 2.05s = 0.49s數組
優化版tree點擊節點性能分析圖:點擊節點處理速度2.53s - 2.35s = 0.18sbash
最終對比是:數據結構
遞歸版tree,渲染速度: 12.19s,點擊節點處理速度: 9.52s
優化版tree,渲染速度: 0.49s,點擊節點處理速度: 0.18s
咱們能夠藉助performance分析一下遞歸組件的耗時點所在,上遞歸組件渲染的性能分析:dom
一、script耗時分析:post
經過圖-1性能瀑布能夠清晰的看到script執行佔了8.9s的時間,經過上圖即圖-5能夠看到script的的調用棧主要集中在建立vue實例時的createChildren上面。
性能
二、render耗時分析:
經過上圖即圖-6能夠清晰的看到render耗時主要集中在Recalculate Style、Layout上面。咱們知道Recalculate Style、Layout主要是樣式計算,所以查看代碼:
發如今遞歸的tree-node組件裏面有不少樣式的計算,10000條子節點就須要計算10000次。
三、DOM結構分析:
由上圖即圖-8咱們能夠看出遞歸組件生成的DOM結構也相應是嵌套的結構,所以DOM不只有縱向的結構也同時有嵌套層次的結構,所以這時控制DOM的數量很難。
由上面的script、render、DOM結構的分析,能夠看出問題癥結所在:建立vue實例過多!
所以咱們若是要優化,那麼只要減小vue實例的建立,那麼問題就獲得瞭解決,所以接下來來看是如何實現的。
來看咱們的開篇思想:
遞歸的本質是棧的讀取
在算法中咱們會遇到不少遞歸實現的案例,全部的遞歸均可以轉換成非遞歸實現,其中轉換的本質是:遞歸是解析器(引擎)來幫咱們作了棧的存取,非遞歸是手動建立棧來模擬棧的存取過程。
萬物都是相通的,遞歸組件也能夠轉換成扁平數組來實現:
一、更改DOM結構成平級結構,點擊節點以及節點的視覺樣式經過操做總的list數據去實現
二、而後使用虛擬長列表來控制vue子組件實例建立的數量。
主要分爲兩部分功能:
一、tree數據和DOM結構的扁平化
二、虛擬長列表控制DOM渲染數量
一、tree數據和DOM結構的扁平化
由上圖咱們能夠看到通過改造以後的tree的DOM結構,父節點和子節點是平級的,在操做子節點時去操做內存中的listData數據來改變相關聯節點的狀態。
咱們再看下listData數據的結構,:
上圖即圖-10結合圖-9的DOM結構能夠對整個功能的實現邏輯一目瞭然:
listData中的每一項的style、checked、path等信息來描述節點的樣式位置和狀態,操做一個節點時經過listData更改相關節點的狀態樣式等信息。
所以最終來寫咱們的代碼:
咱們再看下handleCheckChange的作了什麼:
handleCheckChange 處理了父節點和子節點的check和半選狀態,一切都是操做的listData中的數據。
如今咱們是把全部listData都經過循環渲染出來,其實咱們能夠只渲染可視區的節點數據。
listData數據的扁平化及DOM的扁平化爲接下來咱們接入虛擬長列表的功能提供了可能。
二、虛擬長列表控制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;
}
複製代碼
而後咱們經過滾動條的位置來計算咱們應該要取哪些數據。
主要代碼:
經過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的元素的效果,經典的空間換時間的案例。
歡迎關注葉雨森的知乎專欄:zhuanlan.zhihu.com/p/55528376
參考: