广告位 · 页头(后台-模块管理-新建模块后替换此区域)

JavaScript如何用数组的keys方法获取索引

JavaScript 中,数组的 keys 方法来自 Array.prototype.keys,它返回一个全新的数组迭代器对象。这个迭代器的任务很单一:按顺序产出数组的索引。对普通数组来说,索引就是 0、1、2 这样的数字,而不是像对象属性那样需要转换成字符串。很多开发者在需要同时拿到下标和元素时,第一反应是手动写一个递增变量,但实际上 keys 方法可以让索引的获取更加直接,也更容易与 for...of 这类遍历语法配合。

JavaScript如何用数组的keys方法获取索引

要注意的是,arr.keys() 返回的并不是一个包含索引的数组,而是一个 Array Iterator 迭代器。迭代器本身不会一次性把所有值都算出来,而是在每次调用 next 方法或者被 for...of 消费时才返回下一个值。这种惰性特性在处理长度很大的数组时能减少不必要的内存分配。下面会从基础用法开始,逐步展开它在不同场景下的表现。

一、keys方法的基本返回值与手动迭代

调用数组的 keys 方法后,得到的是一个 Array Iterator 对象。这个对象遵循迭代器协议,也就是说它有一个 next 方法,每调用一次 next 就会返回一个形如 { value: 索引, done: false } 的结果。当所有索引都被遍历完后,next 会返回 { value: undefined, done: true }。下面的代码展示了手动消费迭代器的过程。

const fruits = ['苹果', '香蕉', '橙子'];
const indexIterator = fruits.keys();
console.log(indexIterator.next()); // { value: 0, done: false }
console.log(indexIterator.next()); // { value: 1, done: false }
console.log(indexIterator.next()); // { value: 2, done: false }
console.log(indexIterator.next()); // { value: undefined, done: true }

从输出结果可以清楚地看到,keys 生成的迭代器产出的 value 是数字类型的索引,而不是字符串。这一点与 Object.keys 有着本质区别,后者的返回值是字符串数组。虽然数字索引在访问数组元素时会被自动转换成字符串,但在需要严格比较索引类型的场景中,这种差异仍然可能造成意想不到的问题。

另外,迭代器是一次性的。一旦调用 next 遍历到 done 为 true,再次调用 next 仍然会返回 done 为 true 的结果,无法重置。如果需要多次遍历同一个索引序列,应当重新调用 arr.keys() 来获得新的迭代器,或者使用 Array.from 等方法提前把索引固化成数组。

二、用for...of直接遍历索引

for...of 是消费迭代器最自然的方式。只要一个对象实现了迭代器协议,它就可以直接放在 for...of 的右侧。数组的 keys 方法返回的正是迭代器,因此你可以直接写出下面的循环来获取索引。

const colors = ['red', 'green', 'blue'];
for (const index of colors.keys()) {
  console.log('当前索引:', index);
  console.log('对应元素:', colors[index]);
}

这段代码会依次输出索引 0、1、2,并且在循环体内部可以直接通过 colors[index] 访问到对应的元素。这种写法比传统的三表达式 for 循环更简洁,也避免了手动维护变量 i 可能带来的越界或者忘记递增的问题。同时,因为 for...of 会自动处理迭代器的结束条件,代码的可读性也更好。

不过,如果你的目标只是拿到索引,使用 for...of 配合 keys 是一种轻量的做法;但如果需要同时拿到索引和值,更推荐直接使用数组的 entries 方法,它会产出 [index, value] 这样的二元组。keys 与 entries 的差别在于前者只包含索引,后者同时包含索引和值,两者可以按需选择。

三、把迭代器转换为普通数组的几种方式

有时候你可能并不想在循环中即时消费索引,而是希望一次性得到一个索引数组,比如要将索引传给某个函数,或者进行映射、过滤等操作。这时可以用 Array.from 或扩展运算符把迭代器转换为普通数组。

const letters = ['a', 'b', 'c', 'd'];
const indexArray1 = Array.from(letters.keys());
console.log(indexArray1); // [0, 1, 2, 3]
const indexArray2 = [...letters.keys()];
console.log(indexArray2); // [0, 1, 2, 3]

Array.from 是专门用来从可迭代对象或类数组对象创建数组的方法,它对 Array Iterator 同样有效。扩展运算符 ... 则会在数组字面量或者函数调用中展开可迭代对象,也可以达到同样的效果。两者在功能上基本等价,但在某些环境中扩展运算符的语法更简洁,而 Array.from 在需要映射函数时更方便,比如 Array.from(arr.keys(), i => i * 2) 可以一步生成处理后的索引数组。

需要留意的是,虽然转换成数组后可以反复访问,但这也意味着失去了迭代器的惰性优势。如果数组长度非常大,例如长度达到百万甚至千万级别,提前生成一个完整的索引数组会占用额外内存。在这种情况下,保持迭代器并按需消费通常是更合理的选择。

四、稀疏数组中的表现以及与Object.keys的差异

稀疏数组指的是数组中间存在空位的情况,例如 const arr = [1, , 3]; 这个数组的长度为 3,但索引 1 的位置并没有实际赋值。理解 keys 方法在稀疏数组上的行为,有助于避免使用 Object.keys 时容易出现的索引缺失问题。

const sparseArray = [10, , 30];
console.log(sparseArray.length); // 3
console.log([...sparseArray.keys()]); // [0, 1, 2]
console.log(Object.keys(sparseArray)); // ['0', '2']

从结果可以看出,keys 方法会完整输出从 0 到 length-1 的所有索引,即使对应位置是空位也不会跳过。这是因为数组的 keys 是基于 length 生成索引序列,而不是基于实际存在的属性。相反,Object.keys 只会返回数组对象上真实存在的可枚举属性,因此稀疏数组中的空位索引 1 不会出现在 Object.keys 的结果中。

这个差异在实际开发中很重要。比如你需要构建一个固定长度的列表,即使某些位置暂时没有值,也仍然希望保留这些位置的索引以进行后续填充。此时使用 keys 更符合预期。反过来,如果你只关心已经被赋值的位置,那么 Object.keys 提供的字符串索引可能更接近真实数据分布。理解两者的适用边界,可以避免在稀疏数组处理时踩到索引缺失或者类型不一致的坑。

五、实际开发中的典型场景与注意事项

keys 方法一个常见的应用场景是生成一个从 0 开始的连续数字序列。由于 Array(5) 会创建一个长度为 5 但没有任何元素的稀疏数组,配合 keys 方法可以快速得到 [0,1,2,3,4]。例如 [...Array(5).keys()] 这个表达式即可生成 0 到 4 的索引数组。这在需要初始化列表、生成占位符或者做批量 DOM 渲染时非常有用。

const indexList = [...Array(5).keys()];
console.log(indexList); // [0, 1, 2, 3, 4]
indexList.forEach(i => {
  console.log('生成第', i, '项');
});

另一个需要注意的地方是,迭代器一旦开始遍历,如果原数组被修改,迭代器的行为可能变得难以预测。例如在遍历过程中向数组尾部追加元素,某些实现可能会在迭代时包含新增的索引,也可能不会,这取决于迭代器内部对索引的追踪方式。虽然 ECMAScript 规范对数组迭代器的行为有约束,但为了保证代码行为清晰,通常不建议在遍历过程中修改原数组长度。

此外,keys 方法在老旧浏览器中可能存在兼容性问题,但现代浏览器和 Node.js 都已经完整支持。如果你的项目需要兼容非常老的环境,可以使用一个简单的 for 循环替代,或者引入 polyfill。不过对于绝大多数当前的前端或后端 JavaScript 项目来说,直接使用 keys 方法已经足够安全。

广告位 · 文章底部(后台-模块管理替换此区域)