JavaScript怎么获取数组中某元素的所有索引位置

作者:拾光

时间:2026-11-14

浏览:242

详细讲解在 JavaScript 中获取数组某元素全部匹配索引位置的多种高效方法,涵盖 while+indexOf 步进查找、forEach 遍历、reduce 与 flatMap 函数式写法,以及针对 NaN 和对象引用的避坑指南。

文章目录
JavaScript怎么获取数组中某元素的所有索引位置
在 JavaScript 中获取某个元素在数组中的所有索引位置,核心解法主要分为两类:一是基于 indexOf 的第二个参数 fromIndex 在 while 循环中步进跳跃查找,其执行速度最快;二是利用 forEach、reduce 或现代 ES 的 flatMap 遍历全数组,通过条件判断将匹配到的下标收集进结果容器。

一、为什么原生 indexOf 只能得到一个索引?

在日常开发中,许多初学者检索数组元素时,第一反应是调用 arr.indexOf(target)。然而该方法的设计逻辑是短路检索:引擎从数组头部(或指定偏移量)向后单向扫描,一旦发现第一个严格相等(===)的元素,就会立刻返回当前下标并彻底终止检索过程。如果元素在数组中未出现,则返回 -1。

当数组中存在重复元素时,直接调用一次 indexOf 必然会导致漏掉后续所有相同的元素:

// 示例数组:包含多个重复项 'apple'
const fruits = ['apple', 'banana', 'apple', 'orange', 'apple'];

// 普通单次查找:只能拿到第 1 个匹配位置
const firstIndex = fruits.indexOf('apple');
console.log(firstIndex); // 输出: 0 (后面的索引 2 和 4 被完全忽略)

因此,要想获取目标元素出现的所有下标,就必须突破单次查询的限制,在代码中建立一种能够多次定位或全量收集的机制。

JavaScript怎么获取数组中某元素的所有索引位置
图 1:单次 indexOf 与全量索引检索机制原理对比

二、高效方案:while 循环配合 fromIndex 步进检索

这是处理原始类型(字符串、数字、布尔值等)重复元素全量检索中执行效率最高的方案。Array.prototype.indexOf 实际上接收两个参数:indexOf(searchElement, fromIndex),其中第二个参数 fromIndex 用于指定本次搜索的起始偏移位置。

借助这一特性,我们可以把上一次匹配到的索引位置加 1,作为下一次检索的起始点,在 while 循环中反复调用 indexOf,直到返回值变为 -1 为止。

执行步骤与代码实现

  1. 初始化容器与起点:创建用于存放结果的空数组 indices,并将初次检索起点设为 0。
  2. 执行匹配判断:在 while 循环中调用 arr.indexOf(target, startIndex)。
  3. 收集索引与步进:只要返回的下标不等于 -1,就将其压入 indices,并将下一次搜索起点更新为 当前下标 + 1。
  4. 终止并输出:当检索返回 -1 时跳出循环,返回最终收集好的索引列表。
/**
 * 通过 while + indexOf 步进获取目标元素的所有索引
 * @param {Array} arr 源数组
 * @param {*} target 待匹配的目标值
 * @returns {number[]} 包含所有匹配索引的数组
 */
function getAllIndicesWhile(arr, target) {
  const indices = [];
  let index = arr.indexOf(target);

  while (index !== -1) {
    indices.push(index);
    // 从当前匹配项的下一个位置开始继续查找
    index = arr.indexOf(target, index + 1);
  }

  return indices;
}

// 验证效果
const letters = ['a', 'b', 'a', 'c', 'a', 'd'];
console.log(getAllIndicesWhile(letters, 'a')); 
// 示例输出: [0, 2, 4]

console.log(getAllIndicesWhile(letters, 'z')); 
// 示例输出: [] (未出现时返回空数组)

该方案之所以性能优异,是因为 indexOf 是浏览器底层原生 C++ 实现的紧凑循环,跳过了 JavaScript 层面的回调函数执行开销,非常适合数万级甚至数十万级大型数组的快速检索。

JavaScript怎么获取数组中某元素的所有索引位置
图 2:while 配合 fromIndex 步进查询全流程示意图

三、通用直观方案:使用 forEach 遍历收集

虽然 while + indexOf 运行极快,但它的匹配判定仅限于严格相等(===)。在实际业务场景中,我们经常需要处理更灵活的匹配逻辑,例如:忽略大小写的字符串匹配、根据对象的某个主键判断、或者满足特定数学条件(如大于某个值的偶数)。

此时使用 forEach 遍历整个数组是最清晰、容错率最高的方式。其思路是依次查看数组中的每个元素和对应下标,若满足比较条件,就把当前下标推入结果集中。

/**
 * 使用 forEach 收集满足条件的所有索引位置
 * @param {Array} arr 源数组
 * @param {*} target 待匹配的目标值
 * @returns {number[]} 索引数组
 */
function getAllIndicesForEach(arr, target) {
  const indices = [];
  arr.forEach((item, index) => {
    if (item === target) {
      indices.push(index);
    }
  });
  return indices;
}

const scores = [85, 92, 78, 92, 100, 92];
console.log(getAllIndicesForEach(scores, 92)); 
// 示例输出: [1, 3, 5]

如果需要支持自定义匹配规则,只需将匹配逻辑提取为一个断言函数(Predicate),即可轻松检索对象数组中满足条件的全部项:

// 查找所有 status 为 'pending' 的任务索引
const tasks = [
  { id: 101, status: 'done' },
  { id: 102, status: 'pending' },
  { id: 103, status: 'in-progress' },
  { id: 104, status: 'pending' }
];

const pendingIndices = [];
tasks.forEach((task, index) => {
  if (task.status === 'pending') {
    pendingIndices.push(index);
  }
});

console.log(pendingIndices); 
// 示例输出: [1, 3]

四、函数式写法:reduce 与 flatMap 的声明式处理

对于习惯函数式编程或追求无副作用纯函数的项目,也可以使用 Array.prototype.reduce 或 Array.prototype.flatMap 来实现不声明外部可变数组的一行式检索。

1. 使用 reduce 累加器

reduce 允许将初始空数组作为累加器 acc,在遍历过程中将满足条件的下标压入并返回:

function getAllIndicesReduce(arr, target) {
  return arr.reduce((acc, item, index) => {
    if (item === target) {
      acc.push(index);
    }
    return acc;
  }, []);
}

const colors = ['red', 'blue', 'green', 'blue', 'yellow'];
console.log(getAllIndicesReduce(colors, 'blue')); 
// 示例输出: [1, 3]
编写注意

在 reduce 的回调函数中,建议使用 acc.push(index) 并在其后 return acc,切勿在每次迭代中使用展开运算符 [...acc, index]。因为 [...acc, index] 会在每轮循环都重新分配并拷贝一个全新的数组,导致时间复杂度劣化为 O(N²)。

2. 使用 ES2019 的 flatMap 声明式映射

flatMap 结合了 map 和 flat(1) 的功能。利用“空数组在拍平后会消失”的特性:当元素匹配成功时返回 [index],不匹配时返回空数组 [],即可一次性完成过滤与下标提取:

const tags = ['js', 'css', 'html', 'js', 'vue', 'js'];

// 一行代码提取所有 'js' 的索引
const jsIndices = tags.flatMap((item, index) => item === 'js' ? index : []);

console.log(jsIndices); 
// 示例输出: [0, 3, 5]
JavaScript怎么获取数组中某元素的所有索引位置
图 3:四种全量索引获取方案对比与技术选型指南

五、边界场景与特殊值处理注意事项

在实际项目中,数组元素并不总是简单的字符串或整数。如果不注意特殊值与底层机制,容易得到意料之外的错误结果。

1. 检索特殊值 NaN 时的陷阱

在 JavaScript 中,NaN === NaN 的结果是 false。由于 indexOf 内部使用的是严格相等算法(Strict Equality Comparison),因此 arr.indexOf(NaN) 会永远返回 -1,无法匹配到任何索引。

要获取数组中所有 NaN 的索引,应当使用 Number.isNaN() 或 Object.is() 进行判断:

const mixed = [1, NaN, 2, NaN, 3];

// 错误做法:永远返回 []
console.log(getAllIndicesWhile(mixed, NaN)); // 输出: []

// 正确做法:使用 flatMap 结合 Number.isNaN
const nanIndices = mixed.flatMap((item, index) => Number.isNaN(item) ? index : []);
console.log(nanIndices); // 示例输出: [1, 3]

2. 引用类型(对象、数组)的匹配规则

直接在 indexOf 中传入对象字面量(如 indexOf({ id: 1 }))会失败,因为两个内容相同的对象在内存中是完全独立的引用地址。如果需要根据对象引用本身查找,必须传入同一个对象的引用变量;如果是按属性内容查找,应使用基于回调函数的遍历方案。

3. 稀疏数组(Sparse Array)中的空槽现象

如果数组中存在未赋值的空槽(例如 const arr = [1, , 1]),不同的遍历方法处理方式不同:

  • indexOf(undefined) 会检索显式赋值的 undefined,但不会匹配空槽;
  • forEach 会自动跳过空槽,不触发回调;
  • 常规的 for (let i = 0; i < arr.length; i++) 循环在访问空槽位置时会直接得到 undefined。
JavaScript怎么获取数组中某元素的所有索引位置
图 4:边界场景与特殊值处理避坑矩阵

六、工程最佳实践:通用封装函数

为了在生产项目中既拥有 while + indexOf 的高性能,又具备针对对象和自定义条件的灵活性,可以将两者整合成一个通用的索引检索工具函数:

/**
 * 通用获取数组中所有匹配项的索引位置
 * @param {Array} arr 目标数组
 * @param {*|Function} predicateOrValue 待查找的值或匹配谓词函数
 * @returns {number[]} 匹配的所有索引数组
 */
function findAllIndices(arr, predicateOrValue) {
  if (!Array.isArray(arr) || arr.length === 0) {
    return [];
  }

  // 场景 A:传入的是自定义断言函数
  if (typeof predicateOrValue === 'function') {
    const result = [];
    for (let i = 0; i < arr.length; i++) {
      if (predicateOrValue(arr[i], i, arr)) {
        result.push(i);
      }
    }
    return result;
  }

  // 场景 B:查找特殊值 NaN
  if (typeof predicateOrValue === 'number' && Number.isNaN(predicateOrValue)) {
    const result = [];
    for (let i = 0; i < arr.length; i++) {
      if (Number.isNaN(arr[i])) {
        result.push(i);
      }
    }
    return result;
  }

  // 场景 C:常规原始值,采用原生 while + indexOf 高性能跳步
  const result = [];
  let idx = arr.indexOf(predicateOrValue);
  while (idx !== -1) {
    result.push(idx);
    idx = arr.indexOf(predicateOrValue, idx + 1);
  }
  return result;
}

// 验证用例
const sample = [10, 20, 10, 30, 10];
console.log(findAllIndices(sample, 10)); 
// 示例输出: [0, 2, 4]

const users = [{ role: 'admin' }, { role: 'user' }, { role: 'admin' }];
console.log(findAllIndices(users, u => u.role === 'admin')); 
// 示例输出: [0, 2]

const numsWithNaN = [5, NaN, 8, NaN];
console.log(findAllIndices(numsWithNaN, NaN)); 
// 示例输出: [1, 3]

七、方案选型速查表

实现方案 时间复杂度 性能表现 条件灵活性 推荐使用场景
while + indexOf O(N) 极高(原生底层执行) 仅限严格相等 (===) 大体量原始类型数组查找重复值
forEach / for 遍历 O(N) 高(轻微回调损耗) 极高(支持任意表达式) 通用业务逻辑、对象数组匹配、NaN 判断
flatMap 映射 O(N) 中高(有小型数组分配) 高(支持三元表达式) 追求代码精简与现代语法的中小型数组
reduce 累加器 O(N) 中高(纯函数开销) 高(可配合数据管道) 函数式编程规范、链式数据流处理

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章