← 回到手记
代码

at():取倒数第几个,别再 length-1 了

用 Array.prototype.at(-1) 取倒数元素,别再手写 length-1,更别指望 arr[-1];只读访问,写回配合 with()。

要解决的问题

前端里取「最后一个」「倒数第二个」元素太常见了:历史栈顶、分页最后一项、消息列表最新一条。很多人习惯写 arr[arr.length - 1],或者以为像 Python 一样能写 arr[-1]。

后者在 JavaScript 里根本不管用:方括号里的 -1 会被当成字符串属性名 "-1",读到的是普通对象属性,不是数组下标。前者能用,但每次都手算 length,可读性差,还容易写成 length - 2 时漏减、多减。

标准库 Array.prototype.at()(以及字符串的 String.prototype.at())就是干这事的:传入整数下标,负数从末尾往回数。按 MDN,这是 Baseline 广泛可用特性,各主流浏览器自 2022 年 3 月起都已支持(Chrome/Edge 92+、Firefox 90+、Safari 15.4+)。本文示例已在本机 Node.js v20.19.2 跑通。

核心概念

记住这几条(以 MDN 为准):

  • 语法:arr.at(index)。index 会转成整数;index >= 0 时与 arr[index] 等价;index < 0 时访问 index + arr.length。
  • 越界:index < -arr.length 或 index >= arr.length 时直接返回 undefined,不会去读奇怪的属性。
  • 只读:at() 只负责取值。不能用它给负数下标赋值;若要用相对下标写回,MDN 建议配合 with()(返回新数组,不改原数组)。
  • 为什么不能 arr[-1]:方括号按属性名取值,arr[-1] 实际是 arr["-1"],与数组长度无关。
  • 通用性:方法是 generic 的,只要有 length 和整数键即可;String、TypedArray 也有同名 at()。

示例代码

正数、负数、越界(本地已跑通):

const arr = [5, 12, 8, 130, 44];

console.log(arr.at(2));   // 8,等同 arr[2]
console.log(arr.at(-1));  // 44,最后一个
console.log(arr.at(-2));  // 130,倒数第二个
console.log(arr[-1]);     // undefined!不是最后一个
console.log(arr[arr.length - 1]); // 44,旧写法

console.log(arr.at(99));  // undefined
console.log(arr.at(-99)); // undefined

取购物车最后一项,比手算 length 干净:

const cart = ["apple", "banana", "pear"];

function returnLast(list) {
  return list.at(-1);
}

console.log(returnLast(cart)); // "pear"
cart.push("orange");
console.log(returnLast(cart)); // "orange"

三种取「倒数第二个」的对比(MDN 同款思路):

const colors = ["red", "green", "blue"];

const lengthWay = colors[colors.length - 2]; // "green"
const sliceWay = colors.slice(-2, -1)[0];    // "green",多包一层数组
const atWay = colors.at(-2);                 // "green",最直接

字符串同样可以:

console.log("hello".at(-1)); // "o"

需要「相对下标写回」时用 with()(返回新数组):

const nums = [1, 2, 3];
const next = nums.with(-1, 42); // [1, 2, 42]
console.log(nums);             // [1, 2, 3],原数组不变

常见误区

  1. 以为 arr[-1] 能取最后一个
    不能。它读的是属性 "-1"。赋值 arr[-1] = 99 还会在数组对象上多出一个非索引属性,length 不变,at(-1) 仍是真正的末项。

  2. 用 at() 当左值赋值
    at() 只返回值,没有 setter。要改相对位置请用 with(),或先算正数下标再用方括号。

  3. 把 slice(-1) 当成 at(-1)
    slice(-1) 返回长度为 1 的新数组;at(-1) 返回元素本身。取单个值时别混。

  4. 忘记越界是 undefined
    空数组 [].at(-1) 是 undefined,和 arr[arr.length - 1] 在空数组上的行为一致,但别误当成抛错。

  5. 老环境未 polyfill 就上线
    Safari 要到 15.4 才有。若还要兼容更旧 WebView,查 Can I use 或加 polyfill;现代 evergreen 浏览器与 Node 16.6+ 一般没问题。

小结

取倒数第 n 个,优先 arr.at(-n):比 length - n 好读,也比误用 arr[-1] 正确。它是只读访问;越界返回 undefined;写回相对下标用 with()。字符串同样有 at()。正式项目以 MDN Array.prototype.at() 与目标运行时兼容表为准。

参考资料

  1. Array.prototype.at() - MDN
  2. String.prototype.at() - MDN
  3. Array.prototype.with() - MDN
  4. Can I use: Array.prototype.at
✳

谢谢你读到这里。