由於項目緣故,須要實現一個數組轉樹形結構的數據,主要是用於樹組件的渲染。一開始沒有去網絡上找相關的成熟的算法,本身思考本身寫了一個,能夠用,也用了一段時間。可是有一次,數據量特別大,數據又有些特別(非特殊的樹形結構,子節點判斷一部分是經過 parentId 判斷,一部分又是經過其餘節點判斷),致使須要頻繁調用數組轉樹的方法,結果就是出現性能問題。就 1000+ 的節點,結果數據處理用了 50+s 的時間。 算法
通過一段時間調研和測試,作出如下總結。segmentfault
方法 1:我本身的實現辦法數組
/** * 數組結構轉換爲樹結構數據 * 注意: 數組裏面的對象必定是排序過的,也就是說父級必定在前面,它的子級必定在後面 * @param arrayData 源數據 * @param parentId 父節點字段 * @param parentKey 父節點key值 * @param childrenKey 子節點的key值 * @param idKey id的key值 * @returns {Array} */ function arrayToTree1(arrayData, parentId = '', parentKey = 'pid', childrenKey = 'children', idKey = 'id') { const treeObject = [] // 輸出的結果 const remainderArrayData = [] // 遍歷剩餘的數組 arrayData = cloneDeep(arrayData) arrayData.forEach(function (item) { // 服務端返回的數據, parent沒有的狀況可能返回 null 值, 統一轉換成 '' if (item[parentKey] === null) { item[parentKey] = '' } if (item[parentKey] === parentId) { treeObject.push(item) } else { remainderArrayData.push(item) } }) treeObject.forEach(function (item) { // 子節點遍歷,性能問題也主要在這裏,即便沒有子節點,也會遍歷一次 const _childrenData = arrayToTree(remainderArrayData, item[idKey], parentKey, childrenKey, idKey) _childrenData && _childrenData.length > 0 && (item[childrenKey] = _childrenData) }) return treeObject }
方法 2:網絡
/** * 數組結構轉換爲樹結構數據 * 注意: 數組裏面的對象必定是排序過的,也就是說父級必定在前面,它的子級必定在後面 * @param arrayData 源數據 * @param parentId 父節點字段 * @param parentKey 父節點key值 * @param childrenKey 子節點的key值 * @param idKey id的key值 * @returns {Array} */ export function arrayToTree2(arrayData, parentId = '', parentKey = 'pid', childrenKey = 'children', idKey = 'id') { const result = [] const _arrayData = cloneDeep(arrayData) // 深拷貝 _arrayData.forEach(item => { // 服務端返回的數據, parent沒有的狀況可能返回 null 值, 統一轉換成 '' if (item[parentKey] === null) { item[parentKey] = '' } if (item[parentKey] === parentId) { result.push(item) } }) function recursionFn(treeData, arrayItem) { treeData.forEach(item => { // 找到父節點, 並添加到到 children 中. 若是當前節點不是目標節點的父節點, 就遞歸判斷當前節點的子節點 if (item[idKey] === arrayItem[parentKey]) { if (!item[childrenKey]) { item[childrenKey] = [] } item.children.push(arrayItem) } else if (item[childrenKey] && item[childrenKey].length) { recursionFn(item[childrenKey], arrayItem) } }) } // 經過 reduce 遍歷數組. 經過遍歷每一個元素去構建目標樹數據 return _arrayData.reduce((previousValue, currentValue) => { if (currentValue[parentKey]) { // 該節點是子節點 recursionFn(previousValue, currentValue) } return previousValue }, result) }
方法 3:app
/** * 數組結構轉換爲樹結構數據 * @param arrayData 源數據 * @param parentId 父節點字段 * @param parentKey 父節點key值 * @param childrenKey 子節點的key值 * @param idKey id的key值 * @returns {Array} */ function arrayToTree3(array, parentId = '', parentKey = 'parentId', childrenKey = 'children', idKey = 'id') { let arr = [...array] let rootList = arr.filter((item) => { // 服務端返回的數據, parent沒有的狀況可能返回 null 值, 統一轉換成 '' if (item[parentKey] === null) { item[parentKey] = '' } if (item[parentKey] === parentId) { return item } }) function listToTreeData(rootItem, arr, parentKey, idKey) { rootItem.children = [] arr.forEach((item) => { if (item[parentKey] === rootItem[idKey]) { rootItem.children.push(item) } }) if (rootItem.children.length > 0) { rootItem.children.forEach((item) => { listToTreeData(item, arr, parentKey, idKey) }) } else { return false } } rootList.map((rootItem) => { listToTreeData(rootItem, arr, parentKey, idKey) return rootItem }) return rootList }
測試相關的代碼dom
// 生成樹數據的方法 function generateTreeData(size = 5, deep = 4) { var result = [] var fn = function (parentId, size, level) { for (var i = 0; i < size; i++) { var _id = 'id_' + Math.floor(Math.random() * 10000000000) result.push({ id: _id, parentId: parentId }) if (level < deep) { fn(_id, size, level + 1) } } } fn('', size, 1) return result } // 測試方法 function testMethod(fun, args, tag) { args = cloneDeep(args) console.time(tag) var result = fun.apply(this, args) console.log(result); console.timeEnd(tag) }
測試結果性能
var testData = generateTreeData(7, 5) // 共 19607 條數據, 每一個節點下的子節點都是 7 個, 5 層的深度 // 正序(子節點一定在父節點後面) testMethod(arrayToTree1, [testData, ''], 'arrayToTree1') // 14917.079833984375ms testMethod(arrayToTree2, [testData, ''], 'arrayToTree2') // 3157.8740234375ms testMethod(arrayToTree3, [testData, ''], 'arrayToTree3') // 5573.368896484375ms // 亂序排序 testData = testData.sort(function randomSort() { return Math.random() > 0.5 ? -1 : 1 }) // 亂序 testMethod(arrayToTree1, [_arr, ''], 'arrayToTree1') // 22853.781982421875ms testMethod(arrayToTree2, [_arr, ''], 'arrayToTree2') // 100 ms ~ 300 ms 之間, 看亂序的結果. 但結果是錯的, 子節點有的有缺失 testMethod(arrayToTree3, [_arr, ''], 'arrayToTree3') // 11802.785888671875ms
從結果看,固然我本身寫的算法效率慢的離譜,數據量小還好,數據量一大就災難了。
方法 2,效率是很高,可是對數據有要求,必須是通過排序後的數據纔有用,但實際業務場景中,樹會有跨級排序的場景,因此很難保證是順序的。
比較合適的方法是方法 3測試
我用方法 3 的算法,在個人項目中,時間從原來的 50+ 縮短到 5s+, 提高的仍是很直觀的。this
我想應該是有更好的方法,若是你有更好的算法貼出來一塊兒分享下?code