【前端JS】List/Array數(shù)據(jù) 轉 樹形結構數(shù)據(jù)(適用于側邊欄菜單,多級分類等)

先上完整js代碼供復制,再一步步分析思路,讓你能明白怎么樣思考才能對付這種遞歸類的算法處理

首先說明,函數(shù)命名很重要,因為某種程度上,命名的好壞能夠決定你第一眼看到這個函數(shù),能否想象或理解出它的大致實現(xiàn)思路。之前看網(wǎng)上一些寫法,就是因為他們命名的問題,半天沒理解這個思路,為什么可以這么做?他是怎么想到的。所以自己整理了此篇文章。并規(guī)范了函數(shù)命名
通常有兩種叫法menu和submenus,parent和children,邏輯都一樣,兩種叫法都貼出來,復制后稍加修改即可

// 模擬數(shù)據(jù)
var menuList= [
  { 'id': '1', 'name': '動物', 'pid': '0' },
  { 'id': '2', 'name': '鳥類動物', 'pid': '1' },
  { 'id': '3', 'name': '魚類動物', 'pid': '1' },
  { 'id': '4', 'name': '爬行類動物', 'pid': '1' },
  { 'id': '5', 'name': '杜鵑鳥', 'pid': '2' },
  { 'id': '6', 'name': '喜鵲', 'pid': '2' },
  { 'id': '7', 'name': '鯊魚', 'pid': '3' },
  { 'id': '8', 'name': '比目魚', 'pid': '3' },
  { 'id': '9', 'name': '鯨魚', 'pid': '3' },
  { 'id': '10', 'name': '蜥蜴', 'pid': '4' }
]

/**
 * 封裝完善子級菜單的方法
 * @param {obj} menu 父級菜單對象
 * @param {obj} menuList 菜單數(shù)據(jù)(list或array)
 */
function completeSubmenus(menu, menuList) {
  const submenus = []
  // 1.篩選出符合條件的子菜單
  for (const item of menuList) {
    if (item.pid === menu.id) {
      submenus.push(item)
    }
  }
  // 2.如果子菜單存在,則封裝屬性
  if (submenus.length > 0) {
    // 遞歸封裝子菜單中的子子菜單
    for (const submenu of submenus) {
      completeSubmenus(submenu, menuList)
    }
    menu.submenus = submenus
  }
  // 3.返回封裝后的menu對象
  return menu
}

// 測試代碼
const topMenu = { 'id': '1', 'name': '動物', 'pid': '0' }
completeSubmenus(topMenu, menuList)
console.log(topMenu)
// 模擬數(shù)據(jù)
var moduleList = [
  { 'id': '1', 'name': '動物', 'pid': '0' },
  { 'id': '2', 'name': '鳥類動物', 'pid': '1' },
  { 'id': '3', 'name': '魚類動物', 'pid': '1' },
  { 'id': '4', 'name': '爬行類動物', 'pid': '1' },
  { 'id': '5', 'name': '杜鵑鳥', 'pid': '2' },
  { 'id': '6', 'name': '喜鵲', 'pid': '2' },
  { 'id': '7', 'name': '鯊魚', 'pid': '3' },
  { 'id': '8', 'name': '比目魚', 'pid': '3' },
  { 'id': '9', 'name': '鯨魚', 'pid': '3' },
  { 'id': '10', 'name': '蜥蜴', 'pid': '4' }
]

/**
 * 封裝完善子級分類的方法
 * @param {obj} parentObj 父級分類對象
 * @param {obj} moduleList 分類模塊數(shù)據(jù)(list或array)
 */
function completeChildren(parentObj, moduleList) {
  const children = []
  // 1.篩選出符合條件的子分類
  for (const item of moduleList) {
    if (item.pid === parentObj.id) {
      children.push(item)
    }
  }
  // 2.如果子分類存在,則封裝屬性
  if (children.length > 0) {
    // 遞歸封裝子分類中的子子分類
    for (const item of children) {
      completeChildren(item, moduleList)
    }
    parentObj.children = children
  }
  // 3.返回封裝后的parentObj對象
  return parentObj
}

// 測試代碼
const parentObj = { 'id': '1', 'name': '動物', 'pid': '0' }
completeChildren(parentObj, moduleList)
console.log(parentObj)

分析

1.list轉樹形結構需求簡介

list轉樹形結構是一種前端很常見的需求
比如角色管理的權限層級樹,比如側邊導航欄的動態(tài)多級菜單等等,這種需求實在是太常見了

以角色管理的權限層級樹為例,通常我們數(shù)據(jù)庫設計這種有層級關系的數(shù)據(jù)結構,往往是有個子父級關系字段在里面
比如code與parent_code,id與pid等等,用來表示他們的父級歸屬關系
但是后端寫的方法,往往返回給前端的就是這一堆的list對象數(shù)據(jù),需要前端自己轉化成樹形結構


關系圖.png
關系.png

2.原理分析

那么怎么轉,怎么入手?
是個開發(fā),都他娘的知道肯定要用上循環(huán)遍歷或者遞歸調用之類的東西,但是說清楚這遞歸組裝的思路,多少還是有些難度的
我們一步步入手:
先定義一個list數(shù)據(jù)

// 定義一個list數(shù)據(jù)
var menuList = [
  { 'id': '1', 'name': '動物', 'pid': '0' },
  { 'id': '2', 'name': '鳥類動物', 'pid': '1' },
  { 'id': '3', 'name': '魚類動物', 'pid': '1' },
  { 'id': '4', 'name': '爬行類動物', 'pid': '1' },
  { 'id': '5', 'name': '杜鵑鳥', 'pid': '2' },
  { 'id': '6', 'name': '喜鵲', 'pid': '2' },
  { 'id': '7', 'name': '鯊魚', 'pid': '3' },
  { 'id': '8', 'name': '比目魚', 'pid': '3' },
  { 'id': '9', 'name': '鯨魚', 'pid': '3' },
  { 'id': '10', 'name': '蜥蜴', 'pid': '4' }
]

思路第1步:如果要寫個函數(shù),封裝頂級菜單層下的第一層子菜單,實現(xiàn)是不是很簡單:把頂級菜單對象和list數(shù)據(jù)一起傳入函數(shù),遍歷list,如果pid與頂級菜單id相等,就放入

function completeSubmenus(menu, menuList) {
  const submenus = []
  // 1.篩選出符合條件的子菜單
  for (const item of menuList) {
    if (item.pid === menu.id) {
      submenus.push(item)
    }
  }
  // 2.如果子菜單存在,則封裝屬性
  if (submenus.length > 0) {
    menu.submenus = submenus
  }
  // 3.返回封裝后的menu對象
  return menu
}

const topMenu = { 'id': '1', 'name': '動物', 'pid': '0' }
completeSubmenus(topMenu, menuList)
console.log(topMenu)
測試結果1.png

思路第2步:一級子菜單既然能用該函數(shù)封裝完成,那么遍歷這些一級子菜單,調用相同的方法,是不是也能將這些子菜單的子子菜單一一封裝完畢呢?答案是肯定的
我們稍微改下代碼,加個遞歸即可

function completeSubmenus(menu, menuList) {
  const submenus = []
  // 1.篩選出符合條件的子菜單
  for (const item of menuList) {
    if (item.pid === menu.id) {
      submenus.push(item)
    }
  }
  // 2.如果子菜單存在,則封裝屬性
  if (submenus.length > 0) {
    // 遞歸封裝子菜單中的子子菜單
    for (const submenu of submenus) {
      completeSubmenus(submenu, menuList)
    }
    menu.submenus = submenus
  }
  // 3.返回封裝后的menu對象
  return menu
}

const topMenu = { 'id': '1', 'name': '動物', 'pid': '0' }
completeSubmenus(topMenu, menuList)
console.log(topMenu)
測試結果2.png

完畢

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容