TypeScript动态映射排序数据源键值报undefined错误排查
需求说明
现有存储多个对象的大型数据源(实际对象的键名、键数量未知),需要实现排序规则:
- 遍历所有对象,对每个对象的任意属性键,只要检测到该键下出现从未记录过的新值,就将当前所属对象移动到数组靠前位置
- 后续首次出现新值的对象依次排在第2位、第3位……最终数组最靠前的x个元素,刚好可以覆盖所有属性下全部的不同取值
示例输入
const arr = [ {name: 'john', age: 14, adress: 'xxx'}, // 索引0 {name: 'john', age: 14, adress: 'xxx'}, // 索引1 {name: 'marie', age: 14, adress: 'xxx'}, // 索引2 {name: 'marie', age: 14, adress: 'xxx'}, // 索引3 {name: 'john', age: 15, adress: 'xxx'} // 索引4 ]
期望输出
[ {name: 'john', age: 14, adress: 'xxx'}, // 首次出现name:john、age:14、adress:xxx,排第0位 {name: 'marie', age: 14, adress: 'xxx'}, // 首次出现name:marie,排第1位 {name: 'john', age: 15, adress: 'xxx'}, // 首次出现age:15,排第2位 {name: 'marie', age: 14, adress: 'xxx'}, // 无新值,保留靠后位置 {name: 'john', age: 14, adress: 'xxx'} // 无新值,保留靠后位置 ]
原代码报错&逻辑问题
代码运行中断、逻辑不符合预期的核心问题有4个:
uniqueColumns只做了变量声明,没有初始化赋值,直接访问uniqueColumns[key]会得到undefined,再调用.push()直接触发类型错误中断,这是最直接的崩溃原因。- 没有做值的重复判断:不管当前键值是不是已经出现过,只要遍历到键就做交换、自增计数指针,完全不符合“只有首次出现新值才移动对象”的规则,会导致大量无意义交换,顺序完全混乱。
- 同一个对象如果同时有多个键携带新值,会被重复交换多次,导致计数指针i错误,甚至同一个对象重复占据多个前排位置。
- 每次交换都用
indexOf查找当前对象索引,大数组场景下性能极差,且遍历过程中数组被持续修改,很容易拿到错误索引值,引发更多异常。
修复后的可运行实现
针对大数据源场景做了性能优化,不需要提前感知对象结构,直接适配任意键名、键数量的对象数组:
function filterDS(dataSource) { // 初始化无原型对象存储各键已出现的值,避免原型链属性干扰 const seenValues = Object.create(null); // 下一个可放置带新值对象的前排位置 let insertPointer = 0; const total = dataSource.length; for (let curIdx = 0; curIdx < total; curIdx++) { const curItem = dataSource[curIdx]; const keys = Object.keys(curItem); let hasNewValue = false; // 遍历当前对象所有键,检查是否存在未记录的新值 for (const key of keys) { const val = curItem[key]; // 第一次遇到该键,初始化对应的值集合 if (!seenValues[key]) { seenValues[key] = new Set(); } // 检测到新值,标记当前对象需要前移 if (!seenValues[key].has(val)) { seenValues[key].add(val); hasNewValue = true; } } // 仅当对象携带至少一个新值时,才移动到前排位置 if (hasNewValue) { // 当前位置已经是待插入位置,不需要交换 if (curIdx !== insertPointer) { const temp = dataSource[insertPointer]; dataSource[insertPointer] = curItem; dataSource[curIdx] = temp; } insertPointer++; } } return dataSource; }
实现说明
- 用
Set存储每个键下已出现的值,判重时间复杂度为O(1),相比数组的includes(O(n))更适合大型数据源场景。 - 单个对象就算同时携带多个新值,也只会被移动一次,不会重复占用前排位置。
- 全程只在检测到新值时做一次交换操作,不需要反复查找元素索引,不会出现索引错乱问题。
- 最终
insertPointer的值就是覆盖所有属性不同取值需要的最少元素个数,数组前insertPointer个元素刚好满足全覆盖要求。
内容的提问来源于stack exchange,提问作者haddadtheorc
相关产品推荐
相关产品推荐

