es-toolkit 函数式编程指南:使用 fp.at 在 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
导读
at是 es-toolkit 数组工具中用于"按索引批量取值"的函数,而其函数式编程变体fp.at则把这一能力包装成数据后置(data-last)的管道算子,让它可以与pipe无缝组合。本文以 docs/fp/reference/at.md 为骨架,结合 fp 实现源码、基础数组实现 与 pipe 惰性求值机制,讲解fp.at的用法、参数语义、底层原理,以及它和普通at的取舍关系。
一、fp.at是什么
在 es-toolkit 的函数式编程入口es-toolkit/fp中,所有函数都遵循"先传配置,后传数据"的柯里化约定。fp.at正是这样的一个算子:
import { at, pipe } from 'es-toolkit/fp'; const result = pipe(array, at(indices));它的核心语义是:创建一个函数,该函数从被管道传入的数组中,按给定的索引列表取出对应位置的值。与普通写法at(arr, indices)相比,fp.at把数组参数挪到了最后,从而可以被pipe以自顶向下的顺序串起来执行。
二、基本用法
文档给出的最小示例即展示了完整用法(见 docs/fp/reference/at.md):
import { at, pipe } from 'es-toolkit/fp'; pipe(['a', 'b', 'c'], at([0, -1])); // => ['a', 'c']执行过程可以拆解为两步:
at([0, -1])返回一个等待数据的函数(类型为(array: readonly T[]) => T[]);pipe将数组['a', 'b', 'c']作为数据喂给该函数,得到['a', 'c']。
这正是 fp/intro.md 中描述的 fp 入口的设计哲学:每个es-toolkit/fp函数先用配置参数(这里是indices)构造一个算子,再由pipe提供数据,把上一步的输出作为下一步的输入。
参数与返回值
| 项 | 说明 |
|---|---|
indices | number[](或readonly number[]):要从被管道传入的数组中读取的索引列表 |
| 返回值 | (array: readonly T[]) => T[]:一个把readonly T[]映射为所选值新数组的函数 |
负索引语义
at从被管道传入的数组中的每个索引读取值。负索引从数组末尾倒数,行为与Array.prototype.at一致。例如:
pipe(['a', 'b', 'c'], at([0, -1])); // => ['a', 'c'] // 索引 0 取 'a',索引 -1 从末尾倒数取 'c'三、底层实现:一层薄薄的柯里化包装
从源码结构看,fp.at本身非常轻量,它并不重复实现取值逻辑,而是直接复用了 es-toolkit 基础版的at(见 src/fp/array/at.ts):
import { at as atToolkit } from '../../array/at.ts'; export function at<T>(indices: readonly number[]): (array: readonly T[]) => T[] { return function (array: readonly T[]): T[] { return atToolkit(array, indices); }; }关键点在于:fp 变体只改变调用形态,不改变算法。indices在算子创建时被闭包捕获,等到pipe把数组传入时才真正调用基础版at(arr, indices)。这与 fp/intro.md 中"es-toolkit/fp复用es-toolkit的实现,只改变调用方式"的说明完全吻合。
在 src/fp/array/index.ts 中,at通过export { at } from './at.ts';被统一导出,因此可以从es-toolkit/fp顶层直接导入。
基础版at的取值逻辑
被复用的基础实现位于 src/array/at.ts:
export function at<T>(arr: readonly T[], indices: readonly number[]): T[] { const result = new Array<T>(indices.length); const length = arr.length; for (let i = 0; i < indices.length; i++) { let index = indices[i]; index = Number.isInteger(index) ? index : Math.trunc(index) || 0; if (index < 0) { index += length; } result[i] = arr[index]; } return result; }从实现可以提炼出几个值得注意的细节:
- 返回新数组,不修改原数组:结果数组长度与
indices.length一致,按索引顺序排列(如[2, 0]会得到['c', 'a']而非按原数组顺序)。 - 非整数索引会被转换为整数:使用
Number.isInteger判断,非整数走Math.trunc(index) || 0,即向零取整(1.5变为1,2.9变为2),NaN、0等被规整为0。这一点在 docs/reference/array/at.md 中也有相应示例。 - 负索引处理:负索引加上数组长度得到真实位置,
-1对应最后一个元素。 - 越界索引返回
undefined:基础实现的测试用例(src/array/at.spec.ts)覆盖了at(['a', 'b', 'c'], [2, 4, 0, -4])得到['c', undefined, 'a', undefined]的行为,即超出范围时对应位置为undefined。
测试佐证
fp.at的专项测试位于 src/fp/array/at.spec.ts,验证了它在管道中的实际工作方式:
import { describe, expect, it } from 'vitest'; import { at } from './at.ts'; import { pipe } from '../pipe.ts'; describe('at', () => { it('works in a pipe', () => { expect(pipe(['a', 'b', 'c'], at([0, -1]))).toEqual(['a', 'c']); }); });这组测试同时印证了两点:fp.at从es-toolkit/fp子路径导出可用,且必须配合pipe使用才能拿到数据。
四、与普通at的关系:何时用哪个
文档在"info"提示块中给出了明确的使用建议(见 docs/fp/reference/at.md):
在普通代码中优先使用 es-toolkit 原始版的
at;只有在用pipe组合变换时才使用这个fp变体。
两条路线的对比:
| 场景 | 推荐写法 | 示例 |
|---|---|---|
| 直接、一次性的取值 | 基础版at | at(['a', 'b', 'c', 'd'], [0, -1, -2])→['a', 'd', 'c'] |
| 在变换管道中组合 | fp 版at | pipe(data, at([0, -1]), map(fn)) |
基础版at的完整文档见 docs/reference/array/at.md,签名是at(arr, indices),参数含义为:arr(readonly T[],被取值的数组)和indices(readonly number[],取值索引,负值从末尾计数),返回T[]新数组。
五、在 pipe 管道中组合:与惰性求值的关系
fp.at的价值在于它处在pipe构建的变换链中。pipe的实现位于 src/fp/pipe.ts,它支持最多 15 个函数的从左到右组合,并实现了**惰性融合(lazy fusion)**优化:当连续的惰性算子(如map、filter、take)相邻时,pipe会把它们融合成单趟遍历,每个元素一次性穿过所有步骤,不再在步骤之间构造中间数组,遇到take等短路算子还会提前终止遍历。
需要说明的是,从源码结构看,fp.at本身是一个普通(非惰性)算子——它直接调用基础版at并立即返回新数组。因此它天然适合放在管道中靠近数据端的位置(先按索引取子集,再做后续变换),而不是像map/filter那样参与惰性融合。一个典型的组合示例:
import { at, map, pipe } from 'es-toolkit/fp'; // 先取出第 1、3、-1 个元素,再对它们做变换 pipe( [10, 20, 30, 40, 50], at([1, 3, -1]), // => [20, 40, 50] map(x => x * 2) // => [40, 80, 100] );得益于 contenteditable="false">【免费下载链接】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),仅供参考