先记住这个答案
两者的核心差别在匹配方式。indexOf 接收一个目标值,用严格相等(===)逐个比较,找到第一个相等元素就返回下标,否则返回 -1,适合数字、字符串等原始值的定位。findIndex 接收一个回调函数,对每个元素执行该函数,第一个返回真值的元素下标即为结果,因此可以表达任意条件,比如查找 age 大于 30 的对象,或同时满足多个字段约束的元素。判断规则很简单:你能直接写出要比较的等值就用 indexOf,条件涉及属性、计算或多个约束时就用 findIndex。
- indexOf 按 === 匹配已知值
- findIndex 用谓词表达复杂条件
- 对象按属性查找只能用 findIndex
- 查不到时两者都返回 -1
严格相等与谓词回调的匹配机制差异
indexOf 的签名是 indexOf(searchElement, fromIndex),引擎从 fromIndex 开始用严格相等比较每个元素,命中即返回下标。严格相等意味着不做类型转换,'1' 匹配不到 1;同时 NaN 不等于自身,所以 indexOf 永远无法定位 NaN。它的语义是“找这个值”,调用方必须已经持有要比较的具体值。
findIndex 的签名是 findIndex(callbackFn, thisArg),引擎按升序对每个下标调用回调,传入元素、下标、原数组三个参数,回调返回真值即返回当前下标并停止。匹配逻辑完全由回调决定,可以是属性比较、范围判断或多条件组合。对稀疏数组,findIndex 会访问每个索引,空位以 undefined 作为参数传给回调;而 indexOf 会跳过空位,不会把空位当 undefined 参与比较,所以二者对空位的处理不同。
const users = [
{ id: 1, age: 25 },
{ id: 2, age: 34 },
{ id: 3, age: 41 }
];
// indexOf:匹配已知原始值
const scores = [60, 75, 90];
console.log(scores.indexOf(75));
console.log(scores.indexOf(NaN));
// findIndex:按条件查找对象
console.log(users.findIndex(u => u.age > 30));
console.log(users.findIndex(u => u.id === 99));
// 稀疏数组中的空位
const sparse = [1, , 3];
console.log(sparse.findIndex(x => x === undefined));查看输出与解释
1
-1
1
-1
1演示 indexOf 的等值匹配、NaN 失效,以及 findIndex 按属性条件查找对象和处理稀疏空位。
订单列表中定位第一条超期未支付订单
一个后台列表有 5000 条订单对象,需求是定位第一条金额超过 1000 且状态为 pending 的订单,把它滚动到可视区域。输入是对象数组,约束是条件涉及两个字段且只要第一个命中。用 indexOf 无法表达这种条件,因为你没有现成的目标值可比较;用 findIndex 写 o => o.amount > 1000 && o.status === 'pending',命中即停,平均只需扫描前半部分。
结果返回下标后可直接做 users.splice 或聚焦操作,找不到时返回 -1,代码据此提示无匹配。若后续只需要订单本身不需要位置,应改用 find,避免再用下标回查一次。这里的关键决策是:条件不是单个等值,而是字段组合判断,谓词回调是唯一自然的表达方式,强行用 indexOf 只能先映射出属性数组,多一次遍历还多一份内存。
const orders = [
{ id: 'A1', amount: 300, status: 'paid' },
{ id: 'A2', amount: 1200, status: 'pending' },
{ id: 'A3', amount: 1500, status: 'pending' }
];
const idx = orders.findIndex(
o => o.amount > 1000 && o.status === 'pending'
);
console.log(idx);
console.log(JSON.stringify(orders[idx]));
console.log(orders.findIndex(o => o.status === 'cancelled'));查看输出与解释
1
{"id":"A2","amount":1200,"status":"pending"}
-1findIndex 用复合条件命中第一条超期订单,未命中条件返回 -1,调用方据此分支处理。
NaN、引用相等与空数组的失败边界
indexOf 最常见的失效点是 NaN:因为 NaN !== NaN,indexOf(NaN) 永远返回 -1,必须改用 includes 或 findIndex(Number.isNaN)。第二个失效点是对象:indexOf(someObj) 走的是引用比较,只有同一个对象引用才命中,两个内容相同的新对象比较结果为 -1,这是初学者最常踩的坑。
第三个边界是空数组与 fromIndex 越界:空数组上两者都返回 -1;indexOf 的 fromIndex 为负时从末尾倒数,可能匹配到你没想搜的位置。findIndex 的代价是每个元素都要调用一次回调,热路径上对超大数组做简单等值查找时比 indexOf 略慢,但语义清晰性通常更值得保留。
const list = [1, NaN, 3];
console.log(list.indexOf(NaN));
console.log(list.findIndex(Number.isNaN));
const target = { id: 1 };
const objs = [{ id: 1 }, { id: 2 }];
console.log(objs.indexOf(target));
console.log(objs.findIndex(o => o.id === target.id));
console.log([].indexOf(1));
console.log([].findIndex(x => x > 0));查看输出与解释
-1
1
-1
0
-1
-1验证 NaN 等值失效、对象按引用比较的陷阱,以及空数组上两者统一返回 -1 的行为。
容易答错的地方
- 以为 indexOf 可以按对象属性匹配
- indexOf 只做引用级别的严格相等,{id:1} 不等于另一个 {id:1}。按属性查找对象必须用 findIndex 配合谓词,如 arr.findIndex(o => o.id === 1)。
- 认为 findIndex 找不到时返回 undefined
- 找不到时 findIndex 与 indexOf 一样返回 -1,不是 undefined 或 null。若用 if (idx) 判断会把下标 0 误判为未找到,应显式判断 idx === -1 或 idx >= 0。
面试官还会怎么问?
只需要元素本身不需要下标时该用什么?
用 find,它接收同样的谓词回调,返回第一个满足条件的元素本身而非下标,找不到时返回 undefined。findIndex 适合后续要按位置操作(如 splice)的场景。
indexOf 的第二个参数 fromIndex 有什么用?
指定搜索起点,负数表示从末尾倒数。可用于跳过已知前缀或查找下一个出现位置,配合循环能找到所有匹配下标,但负值换算容易出错,需留意边界。
两者的时间复杂度有区别吗?
都是 O(n) 线性扫描且命中即提前停止。区别在于 findIndex 每次迭代多一次回调调用,常数开销略高;对简单等值查找 indexOf 更轻,但语义匹配性应优先于这点微优化。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。