先记住这个答案
arr.at(-1) 是方法调用,它会把负数索引加上数组长度再去取值,所以得到最后一项。arr[-1] 是普通的属性访问,JavaScript 数组没有负数下标的概念,-1 会被转成字符串 "-1" 当作属性名,除非你专门设置过这个属性,否则就是 undefined。而 at 越界时返回 undefined,不报错;它只能读,不能写。at 是 ES2022 加入的,目标环境不支持时,用 arr[arr.length - 1] 或者引入 polyfill。
at(-n)等于arr[arr.length - n]arr[-1]读的是字符串属性"-1",不是最后一项- 越界和空数组都返回
undefined,不报错 at只能读,改最后一项用arr.with(-1, v)或下标赋值
一段代码看清 at 和方括号
先把几种写法放在一起跑一遍。
const arr = ['a', 'b', 'c']
console.log(arr.at(-1)) // 最后一项
console.log(arr[-1]) // 找的是名为 "-1" 的属性
arr[-1] = 'x' // 只是加了个普通属性
console.log(arr.length, arr.at(-1)) // 长度没变,最后一项也没变
console.log(arr.at(5), arr.at(-4)) // 两头越界
console.log(arr.at(1.7), 'hello'.at(-1)) // 小数向零截断;字符串也能用查看输出与解释
c
undefined
3 c
undefined undefined
b oarr[-1] = 'x' 这一行最能说明问题。它没有改最后一项,也没有改长度,只是在数组对象上挂了一个键为 "-1" 的属性,就像给普通对象加字段。数组的 length 只跟非负整数下标有关。
at 的换算规则是:先把参数转成整数(小数向零截断,不传或 NaN 当 0),负数就加上 length,结果不在 0 到 length - 1 之间就返回 undefined。所以 at(-4) 换算后是 -1,仍然越界。
旧环境怎么兜底
at 属于 ES2022,较老的浏览器和运行时没有这个方法,直接调用会报 TypeError: arr.at is not a function。有两种处理方式。
function at(list, index) {
const i = Math.trunc(index) || 0 // 小数截断,NaN 和 -0 都当 0
const k = i < 0 ? list.length + i : i
return k >= 0 && k < list.length ? list[k] : undefined
}
console.log(at([1, 2, 3], -1), at([1, 2, 3], -4), at([], -1))查看输出与解释
3 undefined undefined只取最后一项的话,直接写 arr[arr.length - 1] 最简单,空数组时是 arr[-1],同样得到 undefined。需要支持任意负数时,用上面这种函数,或者在项目里通过 core-js 引入 polyfill,Babel 配合 useBuiltIns 可以按目标浏览器自动注入。
at的真正好处- 好处主要在链式调用里。比如
list.filter(isValid).at(-1),不用为了拿长度先把中间结果存成变量。以前的替代写法slice(-1)[0]也能做到,但会多创建一个数组,读起来也绕。
容易答错的地方
- 以为 JavaScript 数组支持负数下标
- Python 的
list[-1]是最后一项,JavaScript 不是。方括号里的任何值都会先转成属性名,只有非负整数形式的属性名才算数组元素。 - 想用
at修改元素 arr.at(-1) = 'x'会报错,函数调用的结果不能作为赋值目标,Chrome 和 Node.js 里执行到这一行会抛ReferenceError: Invalid left-hand side in assignment。要改最后一项,写arr[arr.length - 1] = 'x';如果不想改原数组,ES2023 的arr.with(-1, 'x')会返回一个替换后的新数组,它也支持负数索引。- 以为越界会抛错
at越界、空数组调用都只是返回undefined。所以拿到undefined时分不清是越界还是这一项本来就存的undefined,需要区分时先判断长度。
面试官还会怎么问?
at 能用在类数组对象上吗?
可以借用。at 只依赖 length 和数字下标,所以 Array.prototype.at.call(arguments, -1) 能拿到最后一个参数。NodeList 本身没有 at,同样可以这样借用,或者先用 Array.from 转成数组。
at(-0) 返回什么?
返回第一项。-0 转成整数还是 0,不小于 0,所以按正数处理。这也说明「负数从末尾数」是按数值判断的,-0 不算负数。
字符串的 at 和 charAt 有什么不同?
charAt 不支持负数,"abc".charAt(-1) 返回空字符串;at(-1) 返回最后一个字符。越界时 charAt 返回空字符串,at 返回 undefined。两者都按 UTF-16 编码单元取,emoji 这类字符可能只取到半个。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。