先记住这个答案
两者的差别在于怎么判断「找到了」。indexOf 接收一个值,用严格相等 === 和每个元素比,第一个相等的下标就是结果,适合找数字、字符串,或者你手上已经拿着的那个对象引用。findIndex 接收一个回调函数,对每个元素调用它,第一个返回真值的下标就是结果,所以能表达任意条件,比如按 id 找对象、按多个字段组合找。简单的判断方法是:能直接写出要比较的那个值,就用 indexOf;条件涉及属性或计算,就用 findIndex。
indexOf按===比较,适合原始值和已有引用findIndex按回调条件找,适合对象和复杂条件- 对象字面量用
indexOf永远找不到,引用不同 indexOf找不到NaN,用findIndex配合Number.isNaN可以
按值找和按条件找
最常见的场景是在对象数组里找某一项,这时两者的差别一眼就能看出来。
const users = [{ id: 1, age: 25 }, { id: 2, age: 35 }]
const target = users[1] // 手上已有的引用
console.log(users.indexOf({ id: 2, age: 35 })) // 新对象,引用不同
console.log(users.indexOf(target)) // 同一个引用
console.log(users.findIndex((u) => u.age > 30)) // 按条件找
console.log([1, NaN].indexOf(NaN), [1, NaN].findIndex(Number.isNaN))查看输出与解释
-1
1
1
-1 1第一行返回 -1,因为 === 比较对象时比的是引用,也就是「是不是同一个对象」,不是「内容是否一样」。字面量 { id: 2, age: 35 } 是新建的对象,和数组里那个不是同一个。只有拿着同一个引用,比如 target,这时 indexOf 才能找到。
最后一行是 NaN 的情况。NaN === NaN 是 false,所以 indexOf 永远找不到它。findIndex 把判断交给你,传一个 Number.isNaN 就能找到。
严格相等带来的类型问题
indexOf 不做类型转换,数字和字符串是两回事。数据从 URL、表单、localStorage 里读出来时通常是字符串,拿数字去找就会失败。
const ids = ['1', '2', '3'] // 从 URL 参数里拿到的是字符串
console.log(ids.indexOf(2)) // 严格相等,不做类型转换
console.log(ids.indexOf('2'))
console.log(ids.findIndex((id) => Number(id) === 2)) // 在回调里统一类型
console.log([1, 2, 1].indexOf(1, 1), [1, 2, 1].lastIndexOf(1)) // 从下标 1 开始找;从后往前找查看输出与解释
-1
1
1
2 2indexOf 的第二个参数 fromIndex 指定从哪里开始找,适合循环找出所有匹配位置。lastIndexOf 从后往前找。findIndex 没有起始位置参数,但 ES2023 加了 findLastIndex,可以从后往前按条件找。
- 性能上怎么选
- 两者都是从头线性查找,都是
O(n),找到就停。indexOf不需要每次调用函数,开销略小,但普通业务数据量下差别可以忽略。如果同一个数组要按id反复查找,不如先建一个Map,把每次查找降到O(1)。
容易答错的地方
- 用
indexOf找对象 - 这是最常见的错误。
users.indexOf({ id: 2 })永远是-1,不管数组里有没有id为 2 的对象。按内容找对象只能用findIndex或find。 - 用返回值直接做条件判断
if (arr.indexOf(x))在元素位于下标 0 时判断为假,不存在时返回-1反而判断为真,逻辑正好反了。要写arr.indexOf(x) !== -1,只关心有没有的话直接用includes。- 以为两者在稀疏数组上表现一样
[, 1].indexOf(undefined)返回-1,因为indexOf会跳过空位;[, 1].findIndex((x) => x === undefined)返回0,因为findIndex会访问每个下标,空位读出来就是undefined。
面试官还会怎么问?
find 和 findIndex 什么时候用哪个?
需要元素本身用 find,需要位置用 findIndex,比如要 splice 删除或者替换某一项时。find 找不到返回 undefined,如果数组里本来就可能存 undefined,就分不清是没找到还是找到了 undefined,这时用 findIndex 更可靠。
要找出所有满足条件的下标怎么写?
没有内置方法。可以用 reduce 收集,或者写成 arr.map((x, i) => (cond(x) ? i : -1)).filter((i) => i !== -1)。按值找所有位置时,可以用 indexOf 配合 fromIndex 循环查找。
回调里能拿到哪些参数?
findIndex 的回调接收三个参数:当前元素、当前下标、数组本身。findIndex 自己还有第二个参数 thisArg,用来指定回调里的 this,不过用箭头函数时这个参数不起作用。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。