在一些目錄結構、機構層級等展現的場景中,咱們常常會用到一些成熟的樹形插件來進行輕鬆展現,好比ztree等。大多數插件會支持對兩種數據源格式的解析,一種是通用的二維數據結構,一種是樹狀數據結構。對於這兩種數據結構的稱呼在各插件中可能不盡相同,這裏依照二維結構和樹狀結構來稱呼。舉例說明以下:javascript
// 二維數據結構 [{ "id": "001", "name": "總部", "parentId": "0" }, { "id": "002", "name": "二級門店1", "parentId": "001" }, { "id": "003", "name": "三級門店", "parentId": "002" }, { "id": "004", "name": "二級門店2", "parentId": "001" }] // 樹狀數據結構 [{ "id": "001", "name": "總部", "parentId": "0", "children": [{ "id": "002", "name": "二級門店1", "parentId": "001", "children": [{ "id": "003", "name": "三級門店", "parentId": "002", "children": [] }] }, { "id": "004", "name": "二級門店2", "parentId": "001", "children": [] }] }]
但在某些插件中,或在某些特殊場景中,咱們有兩種數據結構之間相互轉換的需求,須要本身寫一個輔助函數來完成。這裏就提供兩個這樣的工具函數來完成數據結構的轉換。java
Note: 要說明的是,工具函數沒有通過大數據量轉換測試,因此對有實時性、大量源數據轉換需求的同窗而言,請自行測試分析,可採起前置或異步等方案處理。因爲自身技術水平的侷限性,算法自己會有性能優化的空間,如有更優處理算法,還望交流分享,謝謝!node
咱們來分開介紹兩種數據結構之間的轉換算法,每一個小結中我會先貼出整個函數的代碼清單,以便你們複製粘貼,而後會簡要說明其中大概的邏輯思路。算法
/** * 將通用的二維數據結構轉換爲樹狀數據結構 * @param {String} rootParentIdValue 表示根節點的父類id值 * @param {String} parentIdName 表示父類id的節點名稱 * @param {String} nodeIdName 表示二維結構中,每一個對象主鍵的名稱 * @param {Array} listData 爲二維結構的數據 * @return {Array} 轉換後的tree結構數據 */ function listToTree(rootParentIdValue, parentIdName, nodeIdName, listData) { if (listData instanceof Array && listData.length > 0 && listData[0][parentIdName]) { var rootList = [], nodeList = [] listData.forEach(function(node, index) { if (node[parentIdName] == rootParentIdValue) { rootList.push(node); } else { nodeList.push(node); } }); if (nodeList.length > 0 && rootList.length > 0) { childrenNodeAdd(rootList, nodeList); return rootList; } else if (rootList.length > 0) { throw new Error("沒有對應的子節點集合"); } else { throw new Error("沒有對應的父類節點集合"); } function childrenNodeAdd(rootNodeList, childrenList) { if (childrenList.length > 0) { rootNodeList.forEach(function(rootNode) { rootNode["children"] = []; var childrenNodeList = childrenList.slice(0); childrenList.forEach(function(childrenNode, childrenIndex) { if (parentIdName in childrenNode && rootNode[nodeIdName] == childrenNode[parentIdName]) { rootNode["children"].push(childrenNode); childrenNodeList.splice(childrenIndex, 1); } }); childrenNodeAdd(rootNode["children"], childrenNodeList); }); } } } else { throw new Error("格式不正確,沒法轉換"); } }
此函數可經過listToTree("0", "parentId", "id", sourceData)
調用測試,sourceData
爲文章開頭給出的二維數據結構舉例。數組
下面簡要介紹一下其中邏輯,第10行是簡要驗證一下入參數據的合法性,而後聲明瞭rootList和nodeList兩個變量。其中rootList爲頂級根節點,nodeList爲其餘子節點集合,第14行到20行的循環即是爲兩個變量賦值,以後根據兩個變量的值進一步判斷數據的合法性。在驗證以後調用childrenNodeAdd這個內部函數,此函數以後將會被遞歸調用,爲每個節點添加指定名稱爲「children」的子節點數組。兩個入參分別是rootNodeList和childrenList,表明父節點集合,和其以後的全部子節點集合。在23行第一次調用時,傳入的即是頂級根節點和其以後的全部子孫節點。下面看這段帶有詳細註解的代碼片斷:性能優化
function childrenNodeAdd(rootNodeList, childrenList) { if (childrenList.length > 0) { // 若是沒有子節點了就結束遞歸 //遍歷父節點集合,在子節點中查找其自身的子節點,並添加到對應的子節點數組中 rootNodeList.forEach(function(rootNode) { rootNode["children"] = []; var childrenNodeList = childrenList.slice(0); //複製一個子節點數據,用於存放剩餘的子節點 //遍歷全部子節點 childrenList.forEach(function(childrenNode, childrenIndex) { if (parentIdName in childrenNode && rootNode[nodeIdName] == childrenNode[parentIdName]) { //根節點的id 等於子節點的父類id rootNode["children"].push(childrenNode); //添加對應節點歸爲子節點 childrenNodeList.splice(childrenIndex, 1);//在剩餘子節點中剔除已經分配過的子節點 } }); childrenNodeAdd(rootNode["children"], childrenNodeList); //剩餘子節點繼續遞歸執行,每次遞歸一次就表示節點增長一級。 }); } }
/** * 將樹狀數據結構轉換爲二維數據結構 * @param {String} childrenName 樹狀結構中子節點名稱 * @param {Array} treeData 樹狀結構數據 * @return {Array} 轉換後的通用二維結構數據 */ function treeToList(childrenName, treeData) { var listData = []; transferTreeData(treeData); function transferTreeData (sourceData) { sourceData.forEach( function(node, nodeIndex) { if(node[childrenName].length > 0) transferTreeData(node[childrenName]); delete node[childrenName]; listData.push(node); }); } return listData; }
此函數可經過treeToList("children", sourceData)
調用測試,sourceData
爲文章開頭給出的樹狀數據結構舉例。這裏的邏輯比較簡單就再也不贅述了。數據結構