如下圖的nodeList是一個標准的樹形結構數組,他的層級最深是三層,在實際工作中我們碰到的樹形結構層級不定,有可能更深,每個節點的屬性也復雜的多, 所以能夠訪問任意層級的方法是首選。這里就以遍歷nodeList並輸出所有id為例。
let nodeList = [
{id: '1-1', children: [{id: '1-2-1'}, {id: '1-2-2'}]},
{id: '2-1'},
{id: '3-1', children: [{id: '3-2-1', children: [{id: '3-3-1'}]}]}
]
方法一: 普通遞歸
function readNodes (nodes = [], arr = []) {
for (let item of nodes) {
arr.push(item.id)
if (item.children && item.children.length) readNodes(item.children, arr)
}
return arr
}
這個方法比較常見,第一點就是你需要用額外的變量保存最后的結果,第二點就是可遞歸的條件是當前訪問節點具有子節點,nodeList的children屬性便是記錄了子節點數據,我們不僅要判斷children是否存在,還得判斷children的長度是否為空。在函數中寫上默認值是一個比較好的習慣,因為你無法保證別人在使用你方法的時候會不會給你傳值,同時可以保證你函數的健壯性。
方法二:利用generator 函數返回的遍歷器
function* gen (nodes = []) {
for (let item of nodes) {
yield item.id
if (item.children && item.children.length) yield* gen(item.children)
}
}
這個方法雖然也用到了遞歸,但相比方法二顯得更加的簡潔,易讀。方法二的核心有三點,第一點是generator函數(生成器函數)的返回結果是一個遍歷器,遍歷器可以被擴展運算符(...),for ... of...,Array.from, 解構賦值等訪問, 通常我們都會輸出數組。第二點是緊跟yield的值會被當成遍歷結果輸出,例如這里的item.id,當我們用for...of...去遍歷的時候,遍歷的就是item.id。第三點就是在生成器函數中訪問生成器函數並期望他可以正常被遍歷訪問的時候必須用yield* ,如上圖的yield* readNodes(item.children), 不然的話直接使用readNodes(item.children),將會導致item.children並不會被遍歷到。
小結一下,以上方法的核心功能就是遍歷樹狀的結構,至於你想最后輸出什么內容,只要改變一下方法中的返回值就行。比如我們想把樹形結構的節點都拉平,變成一維數組的樣子:
又或者我們不想讓最后的一維數組中的父節點還出現children屬性,只要自己希望出現的屬性,我們可以這么做
是舉一反三,順勢而為了。