JS `Array.prototype.keys()`:获取数组索引的迭代器

各位观众,早上好!今天咱们来聊聊 JavaScript 数组里一个挺低调但有时又挺有用的家伙:Array.prototype.keys()。别看它名字平平无奇,关键时刻能帮你解决不少问题。

啥是 Array.prototype.keys()

简单来说,Array.prototype.keys() 是一个方法,它会返回一个新的 迭代器 (iterator) 对象。这个迭代器会按照数组中元素的顺序,依次产生数组的 索引 (index)

换句话说,它不是给你数组里的值,而是给你值的 位置。就像寻宝游戏,它给你的是藏宝图上的坐标,而不是直接把宝藏送到你面前。

语法

这玩意儿的语法简单得不能再简单了:

array.keys()

不需要任何参数,直接调用就行。

返回值

返回一个迭代器对象。这个迭代器会按顺序产生数组中每个元素的索引(从0开始)。

例子:最简单的用法

const arr = ['apple', 'banana', 'cherry'];
const iterator = arr.keys();

console.log(iterator.next().value); // 0
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // undefined (迭代结束)

看到了吧? iterator.next().value 会依次返回数组的索引 0、1 和 2。当迭代结束时,返回 undefined

为什么要用 Array.prototype.keys()

你可能会问:“直接用 for 循环不香吗?for (let i = 0; i < arr.length; i++) 多简单粗暴!”

没错,for 循环确实很直接。但是在某些情况下,Array.prototype.keys() 配合 for...of 循环会更优雅,更方便。尤其是在需要处理 稀疏数组 的时候。

稀疏数组:一个坑

稀疏数组是指数组中并非每个索引都有值。它们可能会有一些“空洞”。这些空洞的值实际上是 undefined,但它们 不占用数组的长度

const sparseArray = [1, , 3, , 5]; // 注意中间的两个逗号,表示空洞

console.log(sparseArray.length); // 5
console.log(sparseArray[1]);      // undefined
console.log(sparseArray[3]);      // undefined

如果用普通的 for 循环遍历稀疏数组,你会遍历到这些空洞,得到 undefined。这可能不是你想要的。

const sparseArray = [1, , 3, , 5];
for (let i = 0; i < sparseArray.length; i++) {
  console.log(`Index ${i}: ${sparseArray[i]}`);
}

// 输出:
// Index 0: 1
// Index 1: undefined
// Index 2: 3
// Index 3: undefined
// Index 4: 5

但是,如果用 Array.prototype.keys() 配合 for...of 循环,就可以跳过这些空洞,只处理实际有值的索引。

const sparseArray = [1, , 3, , 5];
for (const index of sparseArray.keys()) {
  console.log(`Index ${index}: ${sparseArray[index]}`);
}

// 输出:
// Index 0: 1
// Index 2: 3
// Index 4: 5

看,空洞被完美地忽略了!

for...of 循环的威力

for...of 循环是 ES6 引入的一种新的循环方式,它可以遍历 可迭代对象 (iterable)。 数组、字符串、Map、Set 等都是可迭代对象。 Array.prototype.keys() 返回的迭代器对象正好可以被 for...of 循环遍历。

例子:配合 for...of 循环处理更复杂的情况

假设你有一个数组,里面存储的是一些对象。每个对象都有一个 id 属性。你想找到所有 id 大于 10 的对象的索引。

const objects = [
  { id: 5, name: 'apple' },
  { id: 12, name: 'banana' },
  { id: 8, name: 'cherry' },
  { id: 15, name: 'date' }
];

const indices = [];
for (const index of objects.keys()) {
  if (objects[index].id > 10) {
    indices.push(index);
  }
}

console.log(indices); // [1, 3]

在这个例子中,我们用 Array.prototype.keys() 获取了数组的索引,然后用 for...of 循环遍历这些索引。在循环中,我们检查每个对象的 id 属性,如果 id 大于 10,就把对应的索引添加到 indices 数组中。

Array.prototype.entries()Array.prototype.values():两个好兄弟

既然提到了 Array.prototype.keys(),就不得不提一下它的两个好兄弟:Array.prototype.entries()Array.prototype.values()

  • Array.prototype.entries(): 返回一个迭代器对象,该迭代器会产生数组中每个元素的 键值对 (key-value pair)。 键就是索引,值就是对应索引上的元素。

    const arr = ['apple', 'banana', 'cherry'];
    const iterator = arr.entries();
    
    console.log(iterator.next().value); // [0, 'apple']
    console.log(iterator.next().value); // [1, 'banana']
    console.log(iterator.next().value); // [2, 'cherry']
    console.log(iterator.next().value); // undefined

    配合 for...of 循环:

    const arr = ['apple', 'banana', 'cherry'];
    for (const [index, value] of arr.entries()) {
      console.log(`Index ${index}: ${value}`);
    }
    
    // 输出:
    // Index 0: apple
    // Index 1: banana
    // Index 2: cherry
  • Array.prototype.values(): 返回一个迭代器对象,该迭代器会产生数组中每个元素的值。

    const arr = ['apple', 'banana', 'cherry'];
    const iterator = arr.values();
    
    console.log(iterator.next().value); // 'apple'
    console.log(iterator.next().value); // 'banana'
    console.log(iterator.next().value); // 'cherry'
    console.log(iterator.next().value); // undefined

    配合 for...of 循环:

    const arr = ['apple', 'banana', 'cherry'];
    for (const value of arr.values()) {
      console.log(value);
    }
    
    // 输出:
    // apple
    // banana
    // cherry

用表格总结一下这三个方法:

方法 返回值
Array.prototype.keys() 返回一个迭代器,产生数组的索引。
Array.prototype.values() 返回一个迭代器,产生数组的值。
Array.prototype.entries() 返回一个迭代器,产生数组的键值对([索引, 值])。

浏览器兼容性

这三个方法都是 ES6 引入的,所以需要注意浏览器的兼容性。一般来说,现代浏览器都支持这些方法。但是,如果你的代码需要在旧版本的浏览器上运行,就需要使用 polyfill。

Polyfill:给老浏览器打补丁

Polyfill 是一段代码,它可以为旧版本的浏览器提供新特性的支持。 如果浏览器不支持 Array.prototype.keys(),你可以使用下面的 polyfill:

if (!Array.prototype.keys) {
  Array.prototype.keys = function() {
    let index = 0;
    const array = this;

    return {
      next: function() {
        if (index < array.length) {
          return { value: index++, done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  };
}

这段代码会检查浏览器是否支持 Array.prototype.keys()。如果不支持,就自己实现一个。 这段polyfill代码创建了一个实现了迭代器协议的对象,拥有next方法,每次调用next返回下一个索引直到数组结束。

同样的,你也可以为 Array.prototype.values()Array.prototype.entries() 提供 polyfill。

一些高级用法和注意事项

  • 修改原数组的影响: 如果在迭代过程中修改了原数组,迭代器的行为可能会变得难以预测。 最好不要在迭代过程中修改数组的结构(添加或删除元素)。 修改元素的值通常是安全的。

  • 与扩展运算符一起使用: 你可以使用扩展运算符 (...) 将迭代器转换为数组。

    const arr = ['apple', 'banana', 'cherry'];
    const keysArray = [...arr.keys()];
    console.log(keysArray); // [0, 1, 2]
  • 自定义迭代器: Array.prototype.keys() 返回的是一个 默认的 迭代器。 你可以自己实现一个更复杂的迭代器,来满足特定的需求。

总结

Array.prototype.keys() 是一个很有用的小工具,它可以帮助你更方便地遍历数组的索引。 尤其是在处理稀疏数组和需要同时访问索引和值的情况下,它可以让你的代码更简洁,更易读。虽然它不像 map()filter() 那样常用,但在某些场景下,它能发挥奇效。记住,工具不在于多,而在于用得巧。 掌握了 Array.prototype.keys(),你就多了一件解决问题的利器。

希望今天的讲解对大家有所帮助! 咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注