判断一个数组里有没有某个值,数组这事儿看着简单,否包坑却不少。含某我刚学JavaScript那会儿,个值直接写`if (array.indexOf(value) !== -1)`,数组后来发现这写法在特定场景下会翻车。否包今天就把这几种方法掰开揉碎说说。含某
最直观的个值当然是`includes()`。ES2016引入的数组,专门干这个。否包`const has = [1,含某 2, 3].includes(2);` 返回`true`,代码干净得像刚擦过的个值玻璃。但注意——它用的数组是严格相等(`===`),所以`[1,否包 NaN].includes(NaN)`能正确返回`true`,这点跟`indexOf`不一样。含某另外,`includes`对`undefined`和稀疏数组的处理也符合直觉,`[ , ].includes(undefined)`是`true`,但`[ , ].indexOf(undefined)`是`-1`。这俩在对待空槽位时态度完全不同。
再说`indexOf`,老牌方法,1999年就跟着ES3出来了。它返回找到的索引,找不到就返回`-1`。很多人习惯性写`if (arr.indexOf(item) > -1)`,但有个致命伤:它同样用严格相等,却无法识别`NaN`。`[NaN].indexOf(NaN)`返回`-1`,因为`NaN !== NaN`。你要是处理用户输入或数学计算的结果,里面可能藏着`NaN`,这坑能让你调试到怀疑人生。还有,`indexOf`会跳过稀疏数组的空位,`[1, , 3].indexOf(undefined)`也是`-1`。
对象数组呢?这俩方法都歇菜。`includes`和`indexOf`比较的是引用,不是结构。`[{ id: 1}].includes({ id: 1})`返回`false`,因为两个对象在内存里是两个不同的盒子。这时候得用`some()`。`arr.some(item => item.id === 1)`,灵活,想怎么比就怎么比。`some`还有个好处,短路——找到第一个匹配就停,性能上比`filter`强。要是数组有100万条记录,用`filter`查出所有再判断长度,纯属浪费。
性能?别被网上那些微基准测试忽悠了。`includes`和`indexOf`底层都是线性搜索,复杂度`O(n)`,差别在常数项。V8引擎对`indexOf`做了优化,某些情况下比`includes`快那么一丁点,但这点差异在真实业务里可以忽略。真正要注意的是:如果你频繁做包含判断,且数组不变,那就别每次遍历——转成`Set`。`new Set(arr).has(value)`,哈希查找,`O(1)`。我做过一次优化,把一个3000条数据的数组判断逻辑从`includes`换成`Set`,页面卡顿直接消失。但别小数组也上`Set`,创建`Set`本身有开销,少于几十条数据,直接`includes`反而更快。
还有个野路子:`find`。它返回第一个满足条件的元素本身,不是布尔值。`arr.find(v => v === 2) !== undefined` 能判断,但语义不清晰,还得处理元素本身就是`undefined`的情况。除非你在链式调用里顺带要拿那个元素,否则别这么干。
最后说个实际案例。我上周处理一个表单校验,用户勾选的标签ID存在数组里,需要判断某个ID是否已被选。一开始写的`selectedIds.includes(id)`,结果用户快速切换时出现内存泄漏——因为`selectedIds`是响应式的,每次渲染都新建数组,`includes`跟着重复执行。后来改成计算属性里维护一个`Set`,只在ID列表真正变化时更新,问题解决。这就是用`includes`但没考虑性能的典型翻车现场。
判断数组包含值,没有银弹。原始值数组,`includes`够用;对象数组,`some`配条件;频繁查询,上`Set`;要兼容老浏览器(IE11以下),`indexOf`是唯一选择。但2024年了,还在意IE的要么是银行要么是政府项目,你懂我意思。














