由于项目缘故,须要实现一个数组转树形结构的数据,主要是用于树组件的渲染。一开始没有去网络上找相关的成熟的算法,本身思考本身写了一个,能够用,也用了一段时间。可是有一次,数据量特别大,数据又有些特别(非特殊的树形结构,子节点判断一部分是经过 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