core-js 相对索引方法(Relative Indexing Method)完整指南:Array / String / TypedArray 的 `.at()` 负索引读取
2026/9/12 2:30:51 网站建设 项目流程

core-js 相对索引方法(Relative Indexing Method)完整指南:Array / String / TypedArray 的.at()负索引读取

【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js

导读

相对索引方法(Relative Indexing Method)是 TC39 提出的 ECMAScript 提案,为ArrayString%TypedArray%引入统一的at(index)方法,允许通过负索引从末尾读取元素,终结了arr[arr.length - 1]这类冗长写法的历史。本指南以 docs/web/docs/features/proposals/relative-indexing-method.md 为骨架,结合 core-js 仓库中es.array.ates.string.at-alternativees.typed-array.at三个模块的源码与单元测试,讲解该提案在 core-js 中的实现原理、内置方法签名、入口使用方式及边界行为。读完你将掌握.at()的完整语义、core-js 的 polyfill 加载入口,以及如何用单元测试验证 polyfill 的兼容行为。

提案背景与 core-js 中的定位

相对索引方法(Relative Indexing Method)的规范与提案仓库分别见 tc39 规范 与 提案仓库。它解决的核心痛点在于:传统下标arr[-1]在 JavaScript 中返回undefined,开发者只能写arr[arr.length - 1]来取末位元素,可读性差且容易出错。.at()让负索引"从末尾倒数",语义直观且统一。

在 core-js 中,该提案当前处于 Stage 4(已进入 ES2022 正式规范),因此:

  • 它同时出现在core-js/proposals入口(对应提案入口)与core-js/stage入口(对应Stage 4 集合)中;
  • 在 packages/core-js/stage/4.js 中可以看到require('../proposals/relative-indexing-method')的引入语句;
  • 由于已入标准,Array.prototype.atString.prototype.at%TypedArray%.prototype.at也都以es.*前缀(标准模块)形式存在于 packages/core-js/modules 目录。

从源码结构看,core-js 对已进 Stage 4 的提案采用了"双入口"策略:既保留proposals/入口方便按提案名精确引入,也纳入stage/集合统一引入,最终随着正式版发布迁移为es.*标准模块。

内置方法签名(Built-ins signatures)

文档给出了三种内置对象的at方法签名,核心语义一致:

class Array { at(index: int): any; } class String { at(index: int): string; } class %TypedArray% { at(index: int): number; }

三个方法的参数都是整数index,返回值类型分别为any(数组元素)、string(字符串字符)和number(类型化数组元素)。签名背后的统一算法如下:

  1. this强制转换为可索引对象(Object / String / TypedArray);
  2. 取得其长度len
  3. indexToIntegerOrInfinity抽象操作转换为整数relativeIndex
  4. relativeIndex >= 0,则k = relativeIndex;否则k = len + relativeIndex(负索引从末尾倒数);
  5. k < 0 || k >= len,返回undefined,否则返回O[k]

这一算法在三份实现中几乎逐行一致,下面结合源码逐一分析。

源码级实现:三个at模块的异同

1.Array.prototype.at— packages/core-js/modules/es.array.at.js

$({ target: 'Array', proto: true }, { at: function at(index) { var O = toObject(this); var len = lengthOfArrayLike(O); var relativeIndex = toIntegerOrInfinity(index); var k = relativeIndex >= 0 ? relativeIndex : len + relativeIndex; return (k < 0 || k >= len) ? undefined : O[k]; } }); addToUnscopables('at');

关键点:

  • toObject(this):将this包装为对象,保证at.call({ 0: 1, length: 1 }, 0)这类"类数组对象"调用也能工作;
  • lengthOfArrayLike(O):读取对象的length,兼容类数组;
  • toIntegerOrInfinity(index):把参数转成整数(见下文"内部抽象操作");
  • addToUnscopables('at'):将at加入Array.prototype[Symbol.unscopables],避免with (arr) { at }这类历史遗留语法被新方法污染。

2.String.prototype.at— packages/core-js/modules/es.string.at-alternative.js

var charAt = uncurryThis(''.charAt); var FORCED = fails(function () { return '𠮷'.at(-2) !== '\uD842'; }); $({ target: 'String', proto: true, forced: FORCED }, { at: function at(index) { var S = toString(requireObjectCoercible(this)); var len = S.length; var relativeIndex = toIntegerOrInfinity(index); var k = relativeIndex >= 0 ? relativeIndex : len + relativeIndex; return (k < 0 || k >= len) ? undefined : charAt(S, k); } });

与数组版本的区别:

  • forced: FORCED特性探测:通过fails检测运行环境自带的String.prototype.at是否符合规范——检测用例是'𠮷'.at(-2) !== '\uD842''𠮷'是占两个 UTF-16 码元(surrogate pair)的字符,at(-2)规范上应返回第一个码元'\uD842'。如果宿主实现按"码点"而非"码元"取字符(返回'𠮷'),说明实现有误,core-js 就会强制覆盖为自身 polyfill;
  • uncurryThis(''.charAt):以非包装方式借用原生charAt,避免在String子类或代理对象上产生额外开销;
  • 返回越界时同样是undefined,与数组行为对齐。

3.%TypedArray%.prototype.at— packages/core-js/modules/es.typed-array.at.js

exportTypedArrayMethod('at', function at(index) { var O = aTypedArray(this); var len = lengthOfArrayLike(O); var relativeIndex = toIntegerOrInfinity(index); var k = relativeIndex >= 0 ? relativeIndex : len + relativeIndex; return (k < 0 || k >= len) ? undefined : O[k]; });

该模块通过ArrayBufferViewCore.exportTypedArrayMethod一次性为所有类型化数组子类Int8ArrayUint16ArrayFloat64Array等 9 种视图)注册at方法,并用aTypedArray(this)做类型强校验,确保只作用于真正的 TypedArray 实例。返回的仍是number类型的元素值,越界返回undefined

补充:在proposals入口中,TypedArray 部分目前通过 packages/core-js/modules/esnext.typed-array.at.js 间接引用es.typed-array.at(文件头部有// TODO: Remove from core-js@4注释),说明该入口属于提案时代的遗留转发,标准实现始终是es.typed-array.at

内部抽象操作:toIntegerOrInfinity与参数规范化

三份实现都调用了 packages/core-js/internals/to-integer-or-infinity.js:

var trunc = require('../internals/math-trunc'); module.exports = function (argument) { var number = +argument; return number !== number || number === 0 ? 0 : trunc(number); };

该内部模块实现规范中的ToIntegerOrInfinity抽象操作,行为要点:

  • 先把参数一元取正(+argument),转成 number;
  • NaN±0统一归零(number !== number || number === 0 ? 0);
  • 其余值用Math.trunc截断小数部分。

由此推导出.at()的几个实用边界行为(均可由 tests/unit-global/es.array.at.js 中的断言证实):

  • [1, 2, 3].at(NaN)1(NaN 归 0,即取第一个元素);
  • [1].at()1(无参数时undefined转 NaN,同样归 0);
  • [1, 2, 3].at(-0)1-0 >= 0成立,按正索引 0 处理);
  • [1, 2, 3].at(0.4)/at(0.5)/at(0.6)→ 均为1(小数被截断为 0)。

边界行为与测试验证

core-js 的单元测试 tests/unit-global/es.array.at.js 使用 QUnit 对Array#at做了系统验证,几乎逐条覆盖上文算法:

assert.same([1, 2, 3].at(0), 1); assert.same([1, 2, 3].at(3), undefined); // 越界返回 undefined assert.same([1, 2, 3].at(-1), 3); // 负索引从末尾倒数 assert.same([1, 2, 3].at(-4), undefined); // 负索引越界 assert.same([1, 2, 3].at(0.4), 1); // 小数截断 assert.same([1].at(NaN), 1); assert.same([1].at(), 1); assert.same([1, 2, 3].at(-0), 1); assert.same(Array(1).at(0), undefined); // 稀疏空位返回 undefined assert.same(at.call({ 0: 1, length: 1 }, 0), 1); // 类数组对象可用 assert.true('at' in Array.prototype[Symbol.unscopables]); // unscopables 注册

严格模式下还断言了对null/undefined调用会抛TypeError。归纳出的行为规则如下:

调用形式结果说明
arr.at(i)i >= 0i < lenarr[i]正索引按位取
arr.at(-1)末位元素len - 1
arr.at(-len)首位元素恰好倒数到 0
arr.at(i)i >= leni < -lenundefined越界不抛错
arr.at(0.5)/arr.at(NaN)/arr.at()等价于arr.at(0)参数被ToIntegerOrInfinity规范化
at.call(null)/at.call(undefined)TypeError严格模式下不可索引

String#at%TypedArray%#at的越界行为完全一致:返回undefined,不抛异常,也不改变原对象。

Entry points:如何在项目中引入

文档给出的提案入口为:

core-js/proposals/relative-indexing-method

在代码中使用时对应:

import 'core-js/proposals/relative-indexing-method'; // 或 CommonJS require('core-js/proposals/relative-indexing-method');

该入口文件 packages/core-js/proposals/relative-indexing-method.js 的实现非常简洁,本质是三个模块的聚合转发:

'use strict'; // https://github.com/tc39/proposal-relative-indexing-method require('../modules/es.string.at-alternative'); require('../modules/esnext.array.at'); require('../modules/esnext.typed-array.at');

由于该提案已经进入 ES2022 正式规范,core-js 还提供了多种等价或更细粒度的引入方式,可按需选择:

  • 按模块细粒度引入(体积最小):core-js/es/array/atcore-js/es/string/atcore-js/es/typed-array/at,分别对应标准模块 es.array.at.js、es.string.at-alternative.js、es.typed-array.at.js;
  • 按类别引入core-js/es/arraycore-js/es/stringcore-js/es/typed-array
  • 按 Stage 引入core-js/stage(或core-js/stage/4),其中 packages/core-js/stage/4.js 已包含require('../proposals/relative-indexing-method')
  • 全量引入core-js(完整 polyfill)或core-js/stable(仅标准特性,已包含.at())。

关于入口语义的完整说明,可参阅仓库文档 docs/web/docs/usage 中的 "Entry points" 小节。选用建议:若只依赖.at()一个特性且在意包体积,直接用core-js/es/array/at等三个细粒度入口;若项目已统一按core-js/stable全量引入,则无需额外引入。

与其他取元素方式的对比

方式取末位元素越界行为备注
arr[arr.length - 1]繁琐返回undefined需先取length,可读性差
arr.slice(-1)[0]需再解包返回undefined产生新数组,有分配开销
arr.at(-1)直接、直观返回undefined提案语义,不产生新对象

在类型化数组上,typedArray.at(-1)同样避免了typedArray[typedArray.length - 1]的样板代码;而String.prototype.at(-1)则省去了str[str.length - 1]。这就是相对索引方法统一三类的意义:一份直觉(负索引倒数),三种内置类型通用。

小结

  • 相对索引方法为ArrayString%TypedArray%提供统一的at(index)方法,负索引从末尾倒数,越界返回undefined,参数经由ToIntegerOrInfinity规范化(NaN、±0 归零,小数截断);
  • core-js 的三份实现共享同一套核心算法,String#at额外通过forced特性探测修正不规范的宿主实现;
  • 该提案已是 Stage 4(ES2022),可按core-js/proposals/relative-indexing-method提案入口、core-js/es/*/at标准模块入口或core-js/stage集合入口引入;
  • 实现与边界行为均有源码(es.array.at.js、es.string.at-alternative.js、es.typed-array.at.js)与单元测试(tests/unit-global/es.array.at.js)双重背书,可在引入前按上表行为表自行验证运行环境的兼容性。

【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js

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

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

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

立即咨询