尧图精选

JavaScript 数组值判断六种方法详解:indexOf、includes、some、every 等实战对比

🕒 发布时间:2026/9/18 3:47:53 📁 来源:尧图网络
做 JavaScript 开发这几年我几乎每天都在跟数组打交道。尤其是到了 HoRain 云这种云端开发环境里前端要校验接口返回的数据云函数要过滤参数定时任务要判断批量结果数组值判断这六个字看着简单真写起来往往踩出一堆意料之外的坑。你可能遇到过这样的情况[NaN].indexOf(NaN)返回 -1[].every(() false)却返回 true[1, 2, 3, empty].includes(undefined)的结果让人摸不着头脑。这些都是数组判断方法里最容易被忽视的细节。这篇博文就以我在 HoRain 云上实际开发云函数和前端中后台项目的经验为背景把 JavaScript 里六种数组值判断方法一次性拆透。我会从最基础的indexOf到比较现代的find逐个说明它们适合什么场景、性能怎么样、有哪些坑最后再给出一套可以直接抄作业的判断方案。无论你刚学 JavaScript还是在云平台里写 Node.js 云函数这篇文章都能帮你少走弯路。1. 从需求出发数组值判断到底在判断什么1.1 业务场景与核心需求数组是 JavaScript 里最灵活的数据结构也正是因为灵活判断数组里到底有没有某个值、满不满足某个条件成了日常开发里最频繁遇到的需求。拿我在 HoRain 云上做的一个数据同步功能来说云函数会接收一批用户 ID需要先判断某个 ID 是否已经存在于白名单数组里再决定放行还是拦截。这是典型的“值是否存在”判断。但数组判断远不止“是否存在”这么简单。梳理一下常见的场景至少有这么几类判断一个基本类型的值是否在数组中比如判断admin是否在角色列表里。判断一个对象是否在数组中比如判断某个配置项对象是否已经加入配置列表。判断数组中是否存在满足某些复杂条件的元素比如是否存在价格大于 100 的商品。判断数组中的所有元素是否都满足某个条件比如批量校验表单数据是否全部通过。在数组中找到满足条件的第一项并获取这一项的值或者索引而不仅仅想知道true或false。这些场景分别对应了不同的方法选错方法虽然也能实现功能但代码可读性和性能差别很大。1.2 从判断结果反推使用场景在深入六种方法之前先想清楚一个问题你的代码拿到判断结果之后要做什么这会直接影响方法选择。如果你只需要一个布尔值用includes或some最合适如果你还需要知道命中项在数组里的位置indexOf和findIndex能直接给你索引如果你需要拿到命中的那一项数据find是最直接的如果你要做全量校验every才是正解。filter也能用来判断但是它会返回一个新数组如果只是想知道有没有它明显过于“重”了。我在代码评审的时候经常看到有人这样写const arr [1, 2, 3]; // 费尽心思把 filter 结果转成布尔值 const has arr.filter(item item 2).length 0;虽然结果没错但这行代码先创建了一个新数组再取length在数据量大的时候会有不必要的内存和性能开销。你需要的只是一个布尔值用arr.some(item item 2)一行解决而且语义更清楚。数组值判断的本质是在回答“数组中有没有这个值、数组是否满足某种条件”。答案可能是布尔值可能是索引可能是匹配到的项也可能是一个处理后的新数组。清楚了这一点后面每一种方法的适用场景就自然清晰了。2. 六种数组值判断方法逐一拆解2.1 indexOf 与 lastIndexOf最经典但容易漏掉 NaNindexOf是 JavaScript 里历史最悠久的数组判断方法之一它返回指定值在数组中首次出现的索引如果没有找到就返回 -1。lastIndexOf则是从数组末尾开始查找返回最后一次出现的索引。const roleList [admin, editor, viewer]; const pos roleList.indexOf(editor); console.log(pos); // 1 const posNot roleList.indexOf(owner); console.log(posNot); // -1很多老项目里判断数组是否包含某个值就是靠indexOf ! -1这个判断模式直到今天这个写法依然完全可用。它最大的优势是兼容性极好在非常老的运行环境里也能跑并且能返回索引方便后续做删除、替换操作。但是indexOf有两个明显的坑第一个坑是它使用严格相等比较判断不了NaN。因为NaN ! NaN所以[NaN].indexOf(NaN)的结果是 -1。这在处理表单提交时很常见用户输入了非数字内容被Number()转成了NaN你往数组里一查发现怎么都查不到。第二个坑是它无法进行复杂条件判断。你想判断数组里有没有大于 100 的数字indexOf完全帮不上忙只能写循环或换用其他方法。lastIndexOf的实际应用场景相对少一些但也不是没有。比如你有一组时间戳想知道某个时间戳在数组中最后一次出现的位置直接用lastIndexOf就能拿到。它同样受NaN问题困扰使用时注意避开。2.2 includes语义最清晰的“是否包含”includes是 ECMAScript 2016 引入的方法语法和语义都非常直接判断数组是否包含某个值返回布尔值。const data [1, 2, 3, NaN]; console.log(data.includes(2)); // true console.log(data.includes(4)); // false console.log(data.includes(NaN)); // true这是我最推荐用来做“基本类型值是否存在”判断的方法。原因有三个一是语义清楚读代码的人一眼就能明白你是在做包含判断二是在NaN的处理上includes使用了 SameValueZero 比较算法NaN能被正确识别三是undefined和null也都能正常判断不会有意外结果。const arr [undefined, null, ok]; console.log(arr.includes(null)); // true console.log(arr.includes(undefined)); // trueincludes还有一个可选的第二个参数fromIndex可以从指定位置开始查找const arr [10, 20, 30, 20]; console.log(arr.includes(20, 2)); // true从索引 2 开始找 console.log(arr.includes(20, 3)); // true索引 3 这个位置就是 20注意fromIndex如果传入负值会从数组末尾开始往前计算起始位置。比如arr.includes(20, -2)的意思是从倒数第二个元素开始往后查。在 HoRain 云的边缘函数或者云函数环境里includes的兼容性基本不用担心。只要你的 Node.js 版本在 6 以上、浏览器不是特别老的版本都能直接用。即使遇到老环境也可以通过 polyfill 解决。不过要记住includes和indexOf一样对“对象”的判断是基于引用而不是内容。两个内容相同的对象只要不是在数组里存的那同一个引用includes都会返回falseconst config { id: 1, name: gateway }; const configList [config]; console.log(configList.includes({ id: 1, name: gateway })); // false console.log(configList.includes(config)); // true这个特性在配置项去重、对象数据校验时特别容易踩坑后面我会专门展开讲。2.3 some回调条件判断的利器some方法会遍历数组对每个元素执行传入的回调函数只要有一个元素让回调返回truesome就立即返回true后面的元素不再遍历。如果没有元素满足条件返回false。const products [ { name: 键盘, price: 99 }, { name: 显示器, price: 1299 }, { name: 鼠标, price: 59 } ]; const hasExpensive products.some(p p.price 1000); console.log(hasExpensive); // true这是处理“复杂条件判断”的主力方法。includes只能做等值判断遇到“是否存在价格大于某个数”“是否存在包含某个关键字的对象”“是否所有必填字段都已赋值”这类场景就要交给some和every。some的回调函数可以接收三个参数当前元素、当前索引、数组本身。一般场景下用第一个参数就足够了。它还有一个特性是“短路效应”只要发现一个符合条件的就立刻停止遍历。这个特性在数据量很大的数组上非常有用可以节省大量计算时间。我在 HoRain 云写数据校验逻辑时经常用some来判断上传的批量数据里是否有非法值const payload [ { id: a1, score: 85 }, { id: b2, score: -10 }, { id: c3, score: 92 } ]; const hasInvalidScore payload.some(item item.score null || Number.isNaN(Number(item.score)) || item.score 0 ); if (hasInvalidScore) { // 返回错误信息告诉前端批量数据里有非法分数 }some对空数组的返回值要记住空数组调用some永远返回false。这个直觉是对的因为空数组里找不到任何满足条件的元素。2.4 every全量校验场景的默认选择every和some看起来是对称的some是“有一个满足就行”every是“全部满足才行”。every会遍历数组对每个元素执行回调只要有一个元素返回false就会立刻返回false。只有所有元素都返回true最终结果才是true。const scores [92, 85, 99]; const allPass scores.every(s s 60); console.log(allPass); // true const mixedScores [92, 58, 99]; const allPass2 mixedScores.every(s s 60); console.log(allPass2); // falseevery在批量数据校验里是很好的工具。比如云函数要接收一批配置数据可以先用every校验每一条数据都包含必需的字段再入库存档const records [ { key: timeout, value: 30 }, { key: retry, value: 3 } ]; const isValid records.every( r r.key typeof r.key string typeof r.value number ); if (isValid) { // 执行入库逻辑 }这里要注意every和some在空数组上的行为恰好相反。空数组调用every永远返回true这在逻辑上是一个“真空真”的判断。很多初学者在这上面吃过大亏。比如你写了一个条件要求数组里所有内容都大于 0然后传入一个空数组结果居然返回true程序就放行了本来不该放行的数据。我在做参数校验时通常会在调用every之前先检查数组是否为空或者反过来想清楚空数组到底应该算通过还是算不通过。如果空数组应该被拦截就需要额外加一个长度判断。2.5 find 与 findIndex拿到第一个命中项而不只是布尔值find方法返回数组中第一个满足回调条件的元素如果没有满足条件的元素返回undefined。findIndex则返回第一个满足条件的元素的索引没有则返回 -1。const users [ { id: 1, name: 张三, active: true }, { id: 2, name: 李四, active: false }, { id: 3, name: 王五, active: true } ]; const firstActive users.find(u u.active); console.log(firstActive); // { id: 1, name: 张三, active: true } const firstActiveIndex users.findIndex(u u.active); console.log(firstActiveIndex); // 0find和findIndex与some很像都支持回调条件判断也都有短路行为找到第一个结果后就不再继续遍历。区别在于some只告诉你“存在或不存在”而find还告诉你“存在的这一项到底是什么”findIndex告诉你“这一项在哪”。很多场景下判断值是否存在只是第一步紧接着还要用这个值做后续操作。比如从配置列表里找到指定 key 的配置项const configs [ { key: region, value: cn-hangzhou }, { key: timeout, value: 30 } ]; const timeoutConfig configs.find(c c.key timeout); if (timeoutConfig) { // 使用 timeoutConfig.value }这里直接用find非常自然。如果你先用some判断存在再用别的方法去拿值就会产生多余的遍历代码也不够优雅。find有一个使用上的小坑如果数组里确实存在满足条件的元素但这个元素的值为undefined那么find返回undefined和没有找到时的返回值一样。比如[1, undefined, 3].find(x x undefined)返回的是undefined你看不出来到底有没有找到。大多数时候这不是问题因为不会特意去数组里找一个值为undefined的元素。但如果你真的需要区分这两种情况用findIndex就能区分因为它返回 -1 或者一个具体的索引。findIndex另一个常见用途是删除数组中的指定元素。想从数组里删除某个对象常规思路是先indexOf但indexOf判断不了对象属性条件这时可以先findIndex拿到索引再spliceconst items [ { id: x1, type: image }, { id: x2, type: video } ]; const targetIndex items.findIndex(item item.id x2); if (targetIndex -1) { items.splice(targetIndex, 1); }这个模式我在云函数里删配置项时经常用比先map再indexOf那种绕弯子的写法稳定多了。2.6 filter不推荐用于判断但你需要知道它filter方法会创建一个新数组其中包含所有通过回调条件测试的元素。它本质上是“筛选”不是“判断”。const numbers [1, 2, 3, 4, 5, 6]; const evenNumbers numbers.filter(n n % 2 0); console.log(evenNumbers); // [2, 4, 6]有些同学会用filter(...).length 0来判断数组里是否存在某个值。这在数据量小的时候没有明显问题但从性能和语义上说都不够好性能上filter会返回一个新数组需要额外内存。语义上filter().length 0不如some()直观读代码的人要多想一层。所以如果你只是想判断“有没有”别用filter。但filter在数据清洗、批量去重、筛选列表等领域是无可替代的。举例来说从前端传过来的数组里筛掉所有空值再判断剩余数据是否为空const rawData [a, , null, b, undefined]; const cleaned rawData.filter(item item ! item ! null); console.log(cleaned); // [a, b] console.log(cleaned.length 0); // false这种组合场景里filter负责清洗长度判断负责结果判断分工明确。filter配合indexOf还能实现数组去重这也是网上最常见的数组去重方案之一const duplicated [1, 2, 2, 3, 3, 3]; const unique duplicated.filter((item, index) duplicated.indexOf(item) index); console.log(unique); // [1, 2, 3]原理是利用了indexOf返回首次出现索引的特性一个元素如果是第一次出现那么它出现的索引一定等于当前遍历索引如果之前已经出现过indexOf返回的是更早的索引当前元素就会被过滤掉。不过这种方式有个缺点对NaN无能为力因为indexOf识别不了NaN。对简单数据类型用这个方案完全够用如果数据里可能有NaN建议用Set去重。3. 方法对比与性能实测3.1 六种方法选择对照表在 HoRain 云写代码和在前端写代码方法论上没区别但云函数环境对内存和超时常更敏感选对方法能省不必要的开销。我整理了一张对照表方便你根据场景直接选方法。方法返回结果判断类型空数组行为NaN识别适用场景indexOf索引无则 -1等值比较返回 -1不识别老环境兼容、需要索引删除元素lastIndexOf索引无则 -1等值比较返回 -1不识别找最后一次出现的位置includes布尔值等值比较返回 false识别判断基本类型值是否存在some布尔值回调条件返回 false由回调决定是否存在满足条件的元素every布尔值回调条件返回 true由回调决定是否所有元素满足条件find命中的元素无则 undefined回调条件返回 undefined由回调决定获取第一个命中项findIndex命中索引无则 -1回调条件返回 -1由回调决定获取第一个命中项索引看这张表选择逻辑就很清晰了。判断基本类型值是否存在优先includes需要兼容老环境或需要索引用indexOf复杂条件判断优先some和find全量校验用every。3.2 大数组场景下的性能表现数组判断在数据量小的时候用哪种方法差别微乎其微。但在 HoRain 云函数里如果接口要处理上万条数据方法之间的性能差异就必须关注了。我特意在 Node.js 环境里做过一个简单测试构造一个长度为 10 万的数字数组分别测试indexOf、includes、some。条件都是“在数组末尾命中目标值”也就是最差情况需要遍历全部元素。实测下来indexOf和includes这类原生等值查找的性能非常接近底层多数情况下都能利用优化遍历速度很快。some因为每次循环都要执行回调函数会有额外的函数调用开销在大数组上明显比indexOf和includes慢一点。但这不是说some不好。绝大多数业务数组不会达到这种规模而some带来的灵活性和语义清晰度远超那一点性能损耗。真正需要关心性能的场景是数组非常大、判断逻辑在循环里被高频调用、云函数内存本身就很小。这种时候建议把固定值的判断从some改成includes或Set。举个例子如果有一个白名单数组长度好几万每次云函数请求都要判断客户端传上来的 ID 是否在白名单里。用includes是 O(n) 复杂度在频繁调用时可能拖慢响应。更优的做法是初始化一个Set把数组转成 Set 后判断复杂度降到 O(1)const whiteListArray [id1, id2, /* ... 大量数据 */]; const whiteListSet new Set(whiteListArray); function isAllowed(id) { return whiteListSet.has(id); }这种方式对“数组初始化一次判断执行多次”的场景特别有效。在云函数冷启动后Set的构建只发生一次后续判断都很快。3.3 HoRain 云函数环境下小心数组长度和内存云函数环境不是浏览器它的内存上限、单次执行时间都有明确限制。处理超大数组时不仅要注意方法选择还要注意全局数据设计。有一次我在 HoRain 云上写一个导出功能云函数需要判断一批订单 ID 中有哪些不在已处理列表里。最初的实现把已处理列表继续用数组存然后在循环里用indexOf逐个判断。订单一多函数执行时间就明显变长。后来我改成把已处理列表转成Set再循环判断set.has(orderId)执行时间直接降了一个数量级。这个优化没什么高深技巧只是用对了数据结构。另外还要注意在云函数里数组的every和some虽然支持return false提前终止但如果数组本身是分页加载或流式返回的数据一次全部toArray再判断内存占用会比较大。遇到这种场景考虑分批加载、边取边判断而不是把所有数据一次性堆到内存里。4. 高频坑位与排查技巧4.1 NaN 判断不准的问题indexOf和lastIndexOf无法识别NaN这是 JavaScript 数组判断里最经典的坑。来看这段代码const values [3, 5, NaN, 7]; console.log(values.indexOf(NaN)); // -1明明是第 2 个位置 console.log(values.includes(NaN)); // true console.log(values.some(v Number.isNaN(v))); // trueindexOf(NaN)返回 -1是因为indexOf内部用的是严格相等比较而NaN NaN为false。includes用 SameValueZero 算法它认为NaN等于自身所以能正确找到。平时写表单校验的时候最容易碰到这种情况。比如用户输入了一串用逗号分隔的数字你把它split成数组再map(Number)如果某一项输入了非数字Number()就会返回NaN。你想检查数组里有没有非法项用indexOf(NaN)根本查不到必须改用some(Number.isNaN)或者includes(NaN)。建议规则是数组里可能出现NaN时不要用indexOf做存在性判断。如果你拿不准数组内容就用some(Number.isNaN)这样最稳妥。const rawNumbers [12, 45, abc, 78].map(Number); const hasNaN rawNumbers.some(Number.isNaN); console.log(hasNaN); // true4.2 引用类型判断的误区对象、数组、函数这些引用类型在做“值判断”时比基本类型复杂得多。includes和indexOf对引用类型做的是引用判断不是内容判断。两个结构完全相同的对象在内存里是两个不同对象所以includes会返回false。const target { id: 1 }; const list [{ id: 1 }]; console.log(list.includes(target)); // false console.log(list.indexOf(target)); // -1 console.log(list.some(item item.id 1)); // true在我们实际业务里经常需要“按对象里的某个唯一字段”来判断对象是否已存在。正确做法是使用some或find用字段值做条件。还有一个常见需求是给对象数组去重。比如云函数收到两份配置数据要合并并去重我见过很多人在这个上面绕圈子。最简单的方式是利用一个Map或Set保存已经出现过的唯一键const dataList [ { id: 1, name: a }, { id: 2, name: b }, { id: 1, name: a again } ]; const seen new Set(); const uniqueList dataList.filter(item { if (seen.has(item.id)) { return false; } seen.add(item.id); return true; }); console.log(uniqueList);这个方案既保证了去重又保留了每个 id 第一次出现的那条记录。云函数合并数据时这套处理非常实用。4.3 稀疏数组与空洞JavaScript 数组允许出现“空洞”也就是数组的某些索引位置不存在元素。比如new Array(3)会创建一个有 3 个位置但都没有值的稀疏数组[1, , 3]中间就有一个空洞。空洞会导致各种判断方法的行为不一致这是很多人没注意到的细节const sparse [1, , 3]; console.log(sparse.includes(undefined)); // true因为空洞位置会被遍历成 undefined console.log(sparse.indexOf(undefined)); // -1indexOf 会跳过空洞 console.log(sparse.some(item item undefined)); // falsesome 也会跳过空洞你没看错同一个稀疏数组includes认为它包含undefined而some认为它不包含。原因在于数组方法的遍历规则不同。includes会访问空洞位置而indexOf、some、every这些方法在遍历时会跳过不存在的索引。这个坑在接收接口数据时也可能出现。有些接口返回的空值不是简洁的undefined而是[null, null]这种密集数组或者前端数据源用new Array(len)初始化后再赋值只填了一部分就会出现大量空洞。判断需求又是“数组中是否有值”于是不同方法给出不一致的答案。我的建议是在源头消除稀疏数组。收到数据后先做一次清洗把空洞和空值统一处理。比如const source [1, , 3, null, undefined, 5]; const cleaned source.filter(item item ! null); console.log(cleaned); // [1, 3, 5]清洗之后再判断各种方法的行为就一致了。别把稀疏数组的状态带到业务逻辑里否则排查问题的成本非常高。4.4 空数组的错误直觉空数组的行为是面试中非常爱考、实际开发里又很容易出问题的点。总结起来很简单[].includes(x)返回false。[].indexOf(x)返回-1。[].some(fn)返回false。[].every(fn)返回true。[].find(fn)返回undefined。[].findIndex(fn)返回-1。最反直觉的是every。空数组调用every返回true因为every的逻辑是“遍历所有元素全部满足条件”空数组里没有需要检查的元素所以条件不证自明地满足。数学上这叫“全称命题对空集为真”。这就造成了一个很现实的问题用every做全量校验时如果数组意外为空校验会被静默通过。比如云函数接收一个数组参数本意是要求数组里至少要有一个元素并且所有元素都合法结果前端传了一个空数组every直接返回true后面的流程就继续往下走最终可能存了一堆无意义的数据。所以使用every做数据校验时建议先验证数组长度const items getItemsFromRequest(); if (items.length 0 || !items.every(isValid)) { throw new Error(数组不能为空且每项都必须合法); }把空数组的判断放到every之外逻辑才完整。4.5 对象数组与多维数组的判断对象数组的“值”判断往往不是判断整个对象而是判断某个属性。对于多维数组你需要定义清楚“值”的层级。比如表格里的数据是一个二维数组每一行是一个数组每一列是一个单元格。现在要判断整个表格里是否存在某个数值直接includes是不行的因为它只判断第一层数组元素而第一层元素是数组不是数值。正确做法是遍历每一行对每一行用includes判断。const grid [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; function isValueInGrid(grid, target) { return grid.some(row row.includes(target)); } console.log(isValueInGrid(grid, 5)); // true console.log(isValueInGrid(grid, 10)); // false对象数组里的嵌套属性判断更常见。比如判断整个订单列表是否包含某个商品 ID而这个商品 ID 不是直接挂在数组元素上而是挂在order.items里的const orders [ { orderId: A1, items: [{ productId: p1 }, { productId: p2 }] }, { orderId: A2, items: [{ productId: p3 }] } ]; const hasProductP2 orders.some(order order.items.some(item item.productId p2) ); console.log(hasProductP2); // true这种嵌套循环判断在业务里非常常见。some的短路机制在这里尤其有用一旦找到目标内层和外层都会立即停止不会做无用功。5. 最佳实践一套可落地的判断方案5.1 通用判断函数封装代码写多了之后我习惯把数组判断逻辑封装成公共函数放到项目的 utils 里。这样不仅在 HoRain 云的多个云函数之间复用方便前端和 Node.js 端也能共用一套逻辑。下面这个工具函数集合覆盖了我日常开发里绝大多数场景// 判断数组是否包含指定值兼容 NaN使用 SameValueZero function includesValue(arr, value) { return arr.includes(value); } // 判断数组是否包含满足条件的元素 function someBy(arr, predicate) { return arr.some(predicate); } // 判断数组所有元素是否都满足条件空数组视为不通过 function everyByNotAllowEmpty(arr, predicate) { return arr.length 0 arr.every(predicate); } // 判断数组中是否存在重复项基于 JSON 序列化适用于纯数据对象 function hasDuplicate(arr) { const seen new Set(); for (const item of arr) { const key JSON.stringify(item) || String(item); if (seen.has(key)) { return true; } seen.add(key); } return false; }这个everyByNotAllowEmpty是我建议的实际项目写法。需求里说“数组必须非空且全部满足条件”就把空数组判断融合进去避免调用方遗漏。再扩展一个“数组里是否存在至少一个非空有效值”的判断这在处理接口返回数据时很常用const rawList [null, undefined, , hello]; const hasValidItem rawList.some(item item ! item ! null); console.log(hasValidItem); // true5.2 与云函数数据校验结合在云函数里更重要的是把数组判断嵌入到整体校验流程中。拿 HoRain 云上一个用户批量导入功能为例前端上传了一批用户记录云函数接收后需要在入库前做这样几个判断数组为空则直接返回错误。数组里是否存在重复的手机号。数组里每个用户的邮箱是否都合法。是否所有用户的年龄都是有效数字。对应代码结构可以这样设计function validateUsers(users) { // 1. 空数组拦截 if (!Array.isArray(users) || users.length 0) { return { valid: false, reason: 用户列表为空 }; } // 2. 重复手机号判断 const phones new Set(); const hasDuplicatePhone users.some(user { if (phones.has(user.phone)) { return true; } phones.add(user.phone); return false; }); if (hasDuplicatePhone) { return { valid: false, reason: 存在重复手机号 }; } // 3. 邮箱与年龄全量校验 const allFieldsValid users.every(user /^[^\s][^\s]\.[^\s]$/.test(user.email) typeof user.age number user.age 0 user.age 150 ); if (!allFieldsValid) { return { valid: false, reason: 邮箱格式或年龄字段不合法 }; } return { valid: true }; }这里我把“重复手机号”的检查用Set实现了。如果你只是想判断“是否存在重复项”不需要知道重复项有哪些用some加Set的写法最省内存不需要维护一个额外的映射表。5.3 扩展字符串反转、数组转字符串与去重场景在热词里我还看到不少关于数组转字符串的需求。数组判断不一定要在数组里完成有时候先把数组转成其他数据结构判断会更简单。比如判断一个数组里的数字是否连续递增如果数据量不大可以直接转成字符串后比较但没有必要。更稳妥的办法是用every判断相邻元素const sequence [1, 2, 3, 4]; const isIncreasing sequence.every((item, index) index 0 || item sequence[index - 1] ); console.log(isIncreasing); // true数组转字符串做判断最常见的场景是“判断数组是否为空”可以arr.join() 。但这种写法不如arr.length 0直接而且遇到内容全是空格的数组还会误判。尽量少用。去重与判断经常同时出现。判断数组是否有重复项最经典的是用Setconst arr [1, 2, 3, 2]; const hasDuplicate new Set(arr).size ! arr.length; console.log(hasDuplicate); // true这个方法对基本类型数组非常高效。对象数组想去重或判断重复就得靠我前面说的字段键方案。还有一个容易混淆的点数组转字符串后做包含判断。比如要判断某个值在字符串数组里是否存在有人会写arr.join(,).includes(admin)这在小数组内可用但会产生两个问题一是如果数组里有my-admin这样的元素join后的字符串就包含admin造成误判二是特殊字符逗号可能干扰判断。所以能够用includes或some时别绕道字符串。再补充一个新的玩法借助对象属性来做判断。如果数组中每个元素都有唯一的 id可以把数组转成 Map然后用map.has(id)判断const items [ { id: 10, name: A }, { id: 20, name: B } ]; const itemMap new Map(items.map(item [item.id, item])); console.log(itemMap.has(10)); // true console.log(itemMap.get(20)); // { id: 20, name: B }这种做法的好处是既能判断存在性又能快速取出对应数据在多个不同地方重复判断同一个数组时性能优势很明显。我在 HoRain 云上写数据同步和配置合并逻辑时经常用的就是这种组合思路先用new Map做索引再用has、get快速判断和取值比反复find整个数组高效得多。最后再分享一个小技巧。判断数组是否包含某个值时如果你使用的是 Node.js 高版本环境可以大胆使用includes和find它们是语义最清晰的。如果代码要在很老的环境里跑就用indexOf结合 polyfill。如果是对象数组判断属性条件别考虑includes直接上some或find。遇到NaN优先includes(NaN)或some(Number.isNaN)。空数组和every是天然的一对陷阱做校验时一定要手动处理空数组情况。数组判断看起来是 JavaScript 里最基础的操作但恰恰是这些基础操作里的细节决定了代码在上线之后稳不稳定。希望这一篇六种方法的拆解能帮你在 HoRain 云上少写几个 bug、少排查几小时的线上问题。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →