es-toolkit fp 版 `flatten` 详解:与 `pipe` 组合的惰性扁平化方案
2026/9/17 0:55:52 网站建设 项目流程

es-toolkit fp 版flatten详解:与pipe组合的惰性扁平化方案

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

本指南聚焦 es-toolkit 函数式编程(fp)模块中的flatten函数:它返回一个「数据在最后」(data-last)的扁平化函数,专为与pipe串联而设计,支持按指定深度展开嵌套数组,并在管道内实现单层扁平化的惰性求值与短路终止。读完本文,你将掌握 fp 版flatten的调用方式、参数语义、类型签名,以及它背后的惰性管道实现原理与测试验证方法。

一、为什么需要 fp 版的flatten

在普通命令式代码中,es-toolkit 提供的是直接操作数组的flatten,例如flatten(arr, 2)一次性返回扁平化结果。但在函数式编程场景里,我们常常需要把多个变换步骤串联起来:

const result = pipe(array, flatten(depth));

pipe要求每一步都是一个「先配置、后接收数据」的运算符函数。因此 fp 模块的flatten不再直接接收数组,而是接收深度参数depth后返回一个新函数,由pipe把数组喂给这个函数。二者分工明确:

  • 普通代码直接调用flatten(命令式,一次完成);
  • pipe组合多步变换时,使用 fp 版flatten

从源码看,fp 版flatten内部正是复用了命令式实现,再为其附加惰性变换能力:src/fp/array/flatten.tsflattenEager直接调用flattenToolkit(array, depth)(来自src/array/flatten.ts),并通过combineEagerAndLazyFunctions将急执行版本与惰性版本打包成一个运算符。

二、使用方法

fp 版flatten的典型用法是与pipe配合:

import { flatten, pipe } from 'es-toolkit/fp'; pipe([[1], [2, 3], [4]], flatten()); // => [1, 2, 3, 4]

省略depth时只扁平化一层。flatten会把管道输入的数组按depth指定的深度展开,返回一个新数组,原数组不被修改。

参数

参数类型默认值说明
depthnumber(可选)1扁平化的深度。传入2表示最多展开两层嵌套

返回值

(array: readonly T[]) => Array<FlatArray<T[], D>>

返回一个接收只读数组、返回扁平化后新数组的函数。类型层面直接复用 TypeScript 内置工具类型FlatArray<T[], D>,因此depth会被映射为类型参数D,展开结果的元素类型可以精确推导。

三、深度参数的行为细节

depth的语义与原生Array.prototype.flat保持一致,这在源码和测试中都有明确依据:

  • 默认值1:只展开最外层嵌套,flatten()等价于flatten(1)
  • 0、负数、NaN:不进行任何扁平化,返回与原数组等价的数组;
  • 小数:内部通过Math.floor(depth)取整后再比较层级,例如flatten(1.3)等同于flatten(1)
  • Infinity:完全展开所有层级。

src/array/flatten.ts的实现用递归遍历数组:当元素是数组且当前深度小于flooredDepth(即Math.floor(depth)的结果)时继续下钻,否则把元素直接推入结果数组。fp 版src/fp/array/flatten.ts同样先计算flooredDepth,保证两种写法行为一致。

测试用例src/array/flatten.spec.ts[1, [2, [3, [4]]]]验证了上述全部边界:

flatten(originArr); // [1, 2, [3, [4]]] 默认深度 1 flatten(originArr, 2); // [1, 2, 3, [4]] flatten(originArr, Infinity); // [1, 2, 3, 4] flatten(originArr, 0); // [1, [2, [3, [4]]]] 深度 0 不展开 flatten(originArr, NaN); // 同深度 0 flatten(originArr, 1.3); // 同深度 1(向下取整)

四、管道内的惰性求值与短路机制

这是 fp 版flatten区别于普通flatten的核心亮点:当它作为管道中连续惰性函数的一员时,pipe会将其融合进单趟遍历,配合末尾的短路运算符(如take)实现提前终止

惰性变换的构造

src/fp/array/flatten.ts中,惰性版本通过createLazyFunction构造,逐元素地调用内部递归函数emitFlattened:若当前值仍为数组且未超过最大深度,就继续向下一层递归并逐个emit;否则直接把该值推给下游。这正是「一个输入元素可能产生多个输出元素」的展开型(相当于flatMap语义)惰性变换。

pipe 如何融合与短路

pipe会把传入的函数按「是否携带lazy元数据」划分成若干连续分组(见chunkFunctions)。当一组函数全部惰性可感知、且输入是可迭代对象(group.shortCircuit为真或输入不是数组)时,就交给lazyPipe处理(src/fp/pipe.ts):从最后一个函数开始反向组合各变换的 sink,形成一条「推送式」管道,再用单层循环驱动输入,任一阶段返回false便立即break

这条机制的注释位于src/fp/_internal/lazy.ts:它不是生成器方案,而是「推送管道」——每个函数接收下游的 sink,通过emit把值推下去;take这类短路函数返回false立即终止整趟遍历,既没有生成器的挂起/恢复开销,也没有逐元素创建迭代器对象,同时避免每一步都构建中间数组

测试验证

src/fp/array/flatten.spec.ts用 vi.fn 间谍函数直接证明了惰性效果:

const spy = vi.fn((value: number[]) => value); expect(pipe([[1], [2], [3]], map(spy), flatten(), take(2))).toEqual([1, 2]); expect(spy).toHaveBeenCalledTimes(2);

管道只取前 2 个结果,map的间谍函数恰好被调用 2 次——第 3 个元素从未被访问,说明take(2)触发了短路,flattenmap被融合为单趟惰性求值。若没有惰性机制,map会被调用 3 次。

五、选择建议

  • 只在pipe内使用 fp 版flatten:单独处理一个数组时,直接调用flatten更直观,也省去一层函数包装;
  • 需要深度展开:fp 版同样支持flatten(2)flatten(Infinity)等任意深度,与命令式版本语义完全一致;
  • 追求性能的管道:将flattenmapfiltertake等惰性函数相邻放置,pipe会将其融合为单趟短路遍历,避免中间数组分配和多余计算。

六、进一步探索

  • fp 版实现:src/fp/array/flatten.ts
  • 命令式实现:src/array/flatten.ts
  • 惰性原语(createLazyFunctioncombineEagerAndLazyFunctions):src/fp/_internal/lazy.ts
  • pipe的分组与融合逻辑:src/fp/pipe.ts
  • 惰性短路测试:src/fp/array/flatten.spec.ts
  • 边界行为测试:src/array/flatten.spec.ts
  • 相关文档:pipe、普通版flatten

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询