JavaScript怎么获取数组中某元素的所有索引位置
详细讲解在 JavaScript 中获取数组某元素全部匹配索引位置的多种高效方法,涵盖 while+indexOf 步进查找、forEach 遍历、reduce 与 flatMap 函数式写法,以及针对 NaN 和对象引用的避坑指南。
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 被完全忽略)
因此,要想获取目标元素出现的所有下标,就必须突破单次查询的限制,在代码中建立一种能够多次定位或全量收集的机制。
二、高效方案:while 循环配合 fromIndex 步进检索
这是处理原始类型(字符串、数字、布尔值等)重复元素全量检索中执行效率最高的方案。Array.prototype.indexOf 实际上接收两个参数:indexOf(searchElement, fromIndex),其中第二个参数 fromIndex 用于指定本次搜索的起始偏移位置。
借助这一特性,我们可以把上一次匹配到的索引位置加 1,作为下一次检索的起始点,在 while 循环中反复调用 indexOf,直到返回值变为 -1 为止。
执行步骤与代码实现
- 初始化容器与起点:创建用于存放结果的空数组
indices,并将初次检索起点设为 0。 - 执行匹配判断:在
while循环中调用arr.indexOf(target, startIndex)。 - 收集索引与步进:只要返回的下标不等于
-1,就将其压入indices,并将下一次搜索起点更新为当前下标 + 1。 - 终止并输出:当检索返回
-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 层面的回调函数执行开销,非常适合数万级甚至数十万级大型数组的快速检索。
三、通用直观方案:使用 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]
五、边界场景与特殊值处理注意事项
在实际项目中,数组元素并不总是简单的字符串或整数。如果不注意特殊值与底层机制,容易得到意料之外的错误结果。
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。
六、工程最佳实践:通用封装函数
为了在生产项目中既拥有 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) | 中高(纯函数开销) | 高(可配合数据管道) | 函数式编程规范、链式数据流处理 |
js字符串怎么转base64字符串
2026-11-13 21:36:17
js字符串转数组split后去空字符串filter
2026-11-13 18:59:21
js字符串怎么转数组处理emoji不拆分乱码
2026-11-13 18:20:35
js字符串十六进制"ff,ab"怎么转数字数组
2026-11-13 14:45:45
JS将本地时间格式化为YYYY-MM-DD HH:mm:ss的代码实现
2026-11-13 14:05:22








