es-toolkit/compat nth() 详解:兼容 Lodash 的按索引取数组元素函数
【免费下载链接】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
导读
nth()是 es-toolkit 兼容层(es-toolkit/compat)中用于按索引获取数组元素的函数,它 1:1 复刻了 Lodash_.nth的行为,支持负索引从数组末尾计数,并针对越界、null/undefined输入等边界情况做了统一处理。本文将以 docs/compat/reference/array/nth.md 为核心,结合 nth 源码 与其单元测试,完整讲解nth()的签名、参数、返回值、边界行为以及底层实现原理,并给出在实际项目中的使用与迁移建议。
背景:为什么需要nth以及何时不该用它
nth属于es-toolkit/compat兼容层。正如 compat 介绍文档 所述,es-toolkit/compat镜像了 Lodash 的接口与行为,其存在的意义是让已有 Lodash 代码库无需改写调用点即可平滑迁移到 es-toolkit,之后再逐步切换到严格类型的es-toolkit主包。
因此,nth的典型使用场景是旧代码迁移期——当代码中原本写的是_.nth(array, index),你可以把导入路径从lodash直接换成es-toolkit/compat,行为保持一致。
需要特别注意的是,官方文档在函数开头就给出了明确的警告(::: warning):
优先使用数组索引访问由于需要处理
null/undefined输入以及整数转换,nth函数的运行速度较慢。 请改用更快、更现代的数组索引访问方式(array[index]或array.at(index))。
也就是说,如果你的项目没有历史包袱、并不是在迁移 Lodash 代码,那么直接使用原生语法array[index]或array.at(index)即可——nth的存在意义是行为兼容,而不是性能最优。这也是 es-toolkit 设计哲学的一部分:兼容层为了对齐 Lodash 的隐式类型转换等行为,会携带额外逻辑,因此“略大、略慢”(详见 docs/compat/intro.md 中 “How it differs fromes-toolkit” 一节)。
函数签名与类型定义
nth的 TypeScript 签名如下:
const element = nth(array, index);对应源码中的实际定义(src/compat/array/nth.ts):
export function nth<T>(array: ArrayLike<T> | null | undefined, n = 0): T | undefined参数说明
| 参数 | 类型 | 是否可选 | 说明 |
|---|---|---|---|
array | ArrayLike<T> \| null \| undefined | 必填 | 要查询的数组(或类数组对象) |
index | number | 可选 | 要获取元素的索引。为负数时从数组末尾计数。默认值为0 |
返回值
T | undefined:返回指定索引处的元素;如果索引越界,返回undefined。
值得留意的是默认值:index省略时相当于nth(array, 0),即返回数组第一个元素,这与 Lodash_.nth的默认行为一致。
使用方式与代码示例
从es-toolkit/compat导入
import { nth } from 'es-toolkit/compat'; const array = [1, 2, 3<|begin▁of▁sentence|> , 4, 5]; // 正索引 nth(array, 1); // => 2 // 负索引(从末尾计数) nth(array, -1); // => 5 nth(array, -2); // => 4 // 越界索引 nth(array, 10); // => undefined nth(array, -10); // => undefinednull或undefined的输入
null或undefined会被当作undefined处理:
import { nth } from 'es-toolkit/compat'; nth(null, 0); // undefined nth(undefined, 0); // undefined按需导入(独立入口)
与lodash/merge的形态类似,compat 层的每个函数都拥有独立入口,只加载该函数所需的文件,而不是整个es-toolkit/compat模块。这在无法进行 tree-shaking 的环境(如 CommonJSrequire()、React Native、无打包器直接在 Node.js 上运行)中尤其有用(参考 docs/compat/intro.md 的 “Importing individual functions” 一节):
import nth from 'es-toolkit/compat/nth';或 CommonJS 风格:
const nth = require('es-toolkit/compat/nth');源码实现与底层原理
完整的实现非常精简(src/compat/array/nth.ts 全文 27 行):
export function nth<T>(array: ArrayLike<T> | null | undefined, n = 0): T | undefined { if (!isArrayLike(array) || array.length === 0) { return undefined; } n = toInteger(n); if (n < 0) { n += array.length; } return array[n]; }下面逐行拆解其内部逻辑,并结合测试用例印证每一步行为。
第一步:isArrayLike校验与空数组短路
if (!isArrayLike(array) || array.length === 0) { return undefined; }isArrayLike的实现位于 src/compat/predicate/isArrayLike.ts:
export function isArrayLike(value?: any): boolean { return value != null && typeof value !== 'function' && isLength((value as ArrayLike<unknown>).length); }它要求三个条件同时成立:
value != null:排除null和undefined;typeof value !== 'function':函数永远不被视为类数组;value.length是一个合法的“长度值”(isLength,即非负有限整数)。
这意味着:
nth(null, 0)和nth(undefined, 0)返回undefined(对应文档示例与测试中的 “should returnundefinedfor empty arrays” 用例);- 空数组
[]直接返回undefined,无需再做索引计算; - 字符串也是合法的类数组,因此
nth('abc', 0) === 'a'、nth('abc', -1) === 'c'(测试用例 “should support strings” 验证了这一点)。
第二步:toInteger索引整数化
n = toInteger(n);toInteger的实现位于 src/compat/util/toInteger.ts:
export function toInteger(value: any): number { const finite = toFinite(value); const remainder = finite % 1; return remainder ? finite - remainder : finite; }其行为链是toInteger → toFinite → toNumber:
- 小数会被向下取整:
nth(array, 1.6)等效于nth(array, 1)(测试用例 “should coercento an integer” 验证了1.6 → 'b'即索引 1 的结果); - 字符串数字会被转换:
nth(array, '1')等效于nth(array, 1); false、NaN、空字符串等 falsy 值会被转为0,即退化为取第一个元素;Infinity会经toFinite收敛为Number.MAX_VALUE,这必然越界,从而返回undefined(测试用例 “should returnundefinedfor non-indexes” 验证了Infinity与array.length都返回undefined)。
第三步:负索引从末尾计数
if (n < 0) { n += array.length; }当索引为负时,将其加上数组长度转换为正向索引。例如nth([1, 2, 3, 4, 5], -1),n = -1 + 5 = 4,取到array[4] === 5。测试用例 “should work with a negativen” 使用range(1, array.length + 1)对['a','b','c','d']逐次取-1到-4,验证结果为['d', 'c', 'b', 'a']。
注意:如果负索引的绝对值超过数组长度,比如nth(array, -10),则n = -10 + 5 = -5,array[-5]在 JavaScript 中读取到的是对象上的-5属性(不存在),因此返回undefined——这正是文档中“越界返回undefined”的体现。
第四步:返回元素
return array[n];最终通过索引直接读取返回。整个过程没有做任何额外的“是否存在该索引”的显式判断,而是依赖 JavaScript 数组越界访问天然返回undefined的特性。
边界行为一览(附测试佐证)
结合 src/compat/array/nth.spec.ts 中的全部用例,可将nth的边界行为总结如下:
| 输入 | 行为 | 测试用例 |
|---|---|---|
nth(array, index)(正索引) | 返回对应元素 | should get the nth element ofarray |
nth(array, -n)(负索引) | 从末尾计数返回元素 | should work with a negativen |
nth(array, 1.6)/nth(array, '1') | 索引被整数化 / 字符串化转换 | should coercento an integer |
nth(null, n)/nth(undefined, n)/nth([], n) | 返回undefined | should returnundefinedfor empty arrays |
nth('abc', n) | 支持字符串类数组 | should support strings |
nth(array, Infinity)/nth(array, array.length) | 返回undefined(越界) | should returnundefinedfor non-indexes |
其中 “should returnundefinedfor non-indexes” 这个用例还揭示了一个细节:测试特意给数组设置了array[-1] = 3(在数组对象上挂一个-1属性),然后调用nth(array, -1)之外的越界场景,验证nth不会误读这类非索引属性——因为负数索引经过n += array.length之后已经不再是负数,自然不会命中array[-1]这样的属性键。这说明nth的负索引处理是严格基于“真实数组位置”而非属性查找的。
性能注意点与迁移建议
- 新代码请直接用
array.at(index):at是 ES2022 引入的原生方法,同样支持负索引(array.at(-1)即最后一个元素),且没有任何类型转换开销。nth的额外成本主要来自isArrayLike校验和toInteger整数转换——文档明确提示这一点。 - 迁移 Lodash 旧代码时:把
import { nth } from 'lodash'改为import { nth } from 'es-toolkit/compat',调用点无需改动,行为完全一致;后续可再逐步清理为原生at/索引访问。 - 追求极致体积时:使用按需入口
es-toolkit/compat/nth或依赖打包器的 tree-shaking,只打包该函数及其依赖(isArrayLike、toInteger、toFinite、toNumber、isLength)。
小结
nth是 es-toolkit 兼容层中一个“小而精”的函数:27 行源码,通过isArrayLike空值守卫、toInteger索引规范化、负索引长度换算三步,完整复刻了 Lodash_.nth的语义,包括空输入返回undefined、字符串类数组支持、小数与字符串索引的隐式转换等细节,并有覆盖全面的单元测试作为行为契约。理解它的实现,既能让你在迁移 Lodash 代码时放心替换,也能帮你更清晰地认识到何时应该放弃它、改用原生array[index]或array.at(index)。
【免费下载链接】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),仅供参考