js数组扁平化flat实现
拆解 JS 数组扁平化:从 flat() 到手写核心逻辑
处理多层嵌套数据时,前端开发最常遇到的“拆包”动作就是数组扁平化。无论是接口返回的级联配置,还是动态组件传递的表单模型,原始数据往往裹着好几层括号。ES2019 推出的 Array.prototype.flat() 直接抹平了这种结构差异,但知其然更要知其所以然。比起每次遇到深嵌套就去翻文档,把底层状态流转理顺,才能在实际工程中避开性能坑点。
原生 flat() 接受一个可选整数,指定拉平的层级,默认为 1,传入 Infinity 可彻底摊平。它的核心诉求是递归降维,但在生产环境里,如果数据源嵌套深度突破几百层,传统递归极易触碰调用栈上限。这时候就需要切换思路:把隐式的函数压栈,改为显式的内存容器管理。
最基础的还原方式是递归遍历。逐个读取元素,遇到子数组就继续向下钻取,非数组值则直接汇入结果集。代码紧凑且符合直觉,但短板也很明显:深度不可控。一旦遇到恶意构造或历史遗留的深嵌套结构,脚本会瞬间抛出 Maximum call stack size exceeded。理解这种写法,只是为了摸清逐层剥离的数据走向。
规避栈溢出的稳妥路径是引入显式栈。我们不依赖调用栈,而是手动维护一个待处理列表。将原数组整体推入栈顶,开启循环:弹出栈顶元素,校验是否为数组实例。若是,将其所有子项反向推入栈中(反向是为了抵消后进先出带来的顺序错乱);若否,直接追加到结果数组。循环直到容器为空。该方法将递归还为循环,任意层级的嵌套都能平稳消化,且能精准控制每一步的执行状态。
function flattenByStack(arr, depth = Infinity) {
const result = [];
const stack = [...arr]; // 拷贝避免修改原数据
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item) && depth > 0) {
// 反向push保持原始索引顺序
for (let i = item.length - 1; i >= 0; i--) {
stack.push(item[i]);
}
depth--; // 仅在同层连续深入时计数,此处简化演示
} else {
result.push(item);
}
}
return result.reverse(); // 栈弹出的逆序需反转一次
}
真实业务数据极少是干净的完美嵌套。字段常混入字符串、数字甚至 null,部分老项目还会产出带空位的稀疏数组。在落地扁平化逻辑时,必须给类型校验加装防护门:仅对 Array.isArray() 放行,其余类型一律视为叶节点直出。对于含空位的数组,原生 flat() 会保留位置并补全 undefined,若需清理空白占位,可在最终返回值链尾拼接 .filter(v => v !== undefined)。自定义层级控制也别写死递归阈值,改用独立计数器配合条件分支,当标记位归零时强制阻断深入,既满足按需切分,又不浪费计算资源。
数组扁平化看着只是个基础 API,背后的状态调度却藏着不少工程取舍。递归方案胜在代码干净,适合轻量预览;显式栈把控制权交还给开发者,处理不可控深数据时更显从容。日常开发直接上原生 flat(depth) 足够覆盖九成场景,只有在需要定制过滤规则、合并同层节点或优化大批量虚拟渲染时,才值得重写底层逻辑。把工具的运行机理摸透,面对复杂数据结构时自然少些纠结。


还没有评论,来说两句吧...