如何用 Array.prototype.entries 配合 for...of 在遍历数组的同时获取索引和值

entries() 返回的是什么类型的迭代器
先说清楚一个核心概念:Array.prototype.entries() 返回的,是一个标准的数组迭代器对象。这意味着,每次调用它的 next() 方法,产出的都是一个长度为 2 的数组,格式固定为 [索引, 值]。
这里有个常见的“坑”:它产出的不是对象,也不是 Map 或 Set 结构。所以,如果你试图直接用对象解构的语法,比如写成 { index, value } 去接收,代码肯定会报错——这正是很多开发者初次使用时感到困惑的地方。
好在,当我们使用 for...of 循环时,引擎会自动帮我们处理迭代器的 next() 调用。我们只需要关注:如何优雅地拆开每次循环得到的那个二元数组。
for...of 中正确解构 entries() 的写法
答案很明确:必须使用数组解构语法,并且顺序是固定的——第一个位置放索引,第二个位置放值。
下面是一些典型的对比例子:
- ✅ 正确写法:
for (const [i, v] of arr.entries()) { ... }。一步到位,清晰简洁。 - ❌ 错误写法:
for (const {index, value} of arr.entries()) { ... }。如前所述,entries()不产出对象,此路不通。 - ⚠️ 低效写法:
for (const entry of arr.entries()) { const [i, v] = entry; ... }。虽然功能上能达到目的,但多了一次声明和赋值,显得冗余。
来看一个具体的例子,感受一下它的简洁:
const arr = ['a', 'b', 'c'];
for (const [i, v] of arr.entries()) {
console.log(i, v); // 依次输出:0 'a' → 1 'b' → 2 'c'
}
和 for...in、传统 for 循环比有什么区别
这是个好问题。选择哪种遍历方式,很大程度上取决于你想避开哪些“坑”。
for...in 循环设计初衷是遍历对象的可枚举属性,用在数组上时,它会把索引当作字符串来遍历,更麻烦的是,它可能会遍历到数组原型链上后来添加的属性,或者数组对象本身的非数字属性。这通常不是我们想要的结果。
传统的 for (let i = 0; i < arr.length; i++) 循环当然很强大,一切尽在掌控。但代价是,你需要手动维护索引变量 i,小心处理边界条件。此外,它在迭代器语义层面(比如与某些可迭代协议的配合)不如 for...of 直接。
那么 arr.entries() 配合 for...of 的优势在哪?
- 精准:它只产出数组自身、按顺序排列的索引-值对,不会有多余的“杂质”。
- 稳定:即使是稀疏数组(有空位),它也会为每个位置产出对应的索引(值表现为
empty)。 - 灵活:它返回的迭代器可以被多次消费(只要没耗尽),而
for...of每次循环都会使用一个新的迭代。
至于性能,在绝大多数现代应用场景中,它与传统 for 循环的差异微乎其微,完全可以忽略。如果一段代码的性能瓶颈真的卡在循环方式上,那更值得审视的,恐怕是循环体内的业务逻辑本身。
遇到空位或 undefined 元素时的行为
这是 Ja vaScript 数组一个比较微妙的点:“空位”(hole)和显式存入的 undefined 值,在底层是不同的。entries() 方法对两者的处理也有区别。
- 对于空位数组(如
const a = [, , 3];),entries()会产出:[0, empty],[1, empty],[2, 3]。注意,这里的empty表示值真正缺失,并非undefined。 - 对于显式包含
undefined的数组(如const b = [undefined, undefined, 3];),产出则是:[0, undefined],[1, undefined],[2, 3]。
在 for...of 循环中解构时,空位(empty)位置解构出来的 v 也会是 undefined。这就导致了一个问题:你无法通过简单的 v === undefined 来判断,这个位置到底是空位,还是确实存了一个 undefined 值。
如果需要精确区分,就得借助 arr.hasOwnProperty(i) 或者 ES2022 引入的 Object.hasOwn(arr, i) 方法来判断该索引是否为数组的自有属性。
最后,值得再次强调:entries() 是 Array 原型上的方法,它主要服务于标准的 Ja vaScript 数组。对于 TypedArray、类数组对象(如 arguments)或者 NodeList 等,通常需要先将其转换为真正的数组(例如使用 Array.from() 或扩展运算符 ...),或者采用其他更适合的方式来获取索引和值。
