
先把逻辑掰扯清楚:some()
是“找特例”,every()
是“查全对”
我发现很多人搞混some()
和every()
,其实核心逻辑就一句话:some()
是“只要有一个符合条件就行”,every()
是“所有都得符合条件”。打个比方,some()
就像你找钥匙——你翻钱包、翻书包、翻口袋,只要在口袋里找到钥匙,就不用再翻其他地方了,直接喊“找到了”;而every()
就像你检查作业——你要检查每一道题都做对了,只要有一道题错了,就不用再检查后面的,直接打“×”。这俩方法都有“短路求值”的特性,就是碰到符合条件的情况就立刻停止遍历,不用遍历整个数组,这样能节省性能。
举个具体的例子,比如你有一个数组const scores = [85, 90, 78, 92]
,想知道有没有不及格的(<60
),用some(score => score < 60)
,结果是false
;如果数组是[85, 58, 78, 92]
,结果就是true
——因为碰到58的时候,就满足“有一个不及格”的条件了,后面的78和92根本不用看。而every()
呢,比如你想验证所有分数都在60分以上,用scores.every(score => score >= 60)
,第一个例子返回true
,第二个例子返回false
——因为碰到58的时候,就满足“有一个不及格”的条件了,直接返回false
。是不是很直观?
但这里有个细节你得注意:空数组的情况。我之前做一个统计功能时,碰到过空数组调用every()
返回true
的情况,当时没注意,结果统计结果出错了。后来查MDN文档(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/everynofollow)才知道,这是JavaScript的规范:空数组调用every()
会返回true
,因为“没有元素不符合条件”;而空数组调用some()
返回false
,因为“没有元素符合条件”。比如const emptyArr = []
,emptyArr.every(() => true)
返回true
,emptyArr.some(() => true)
返回false
。所以如果你处理的数组可能为空,一定要先判断数组是否为空,再用这两个方法,不然会出问题。比如你可以这样写:if (arr.length === 0) { / 处理空数组的情况 / } else { const result = arr.some(...) }
。
为了让你更清楚,我做了个表格,把两者的核心差异、短路时机、空数组返回值都列出来了:
方法 | 核心逻辑 | 短路时机 | 空数组返回值 |
---|---|---|---|
some() | 存在至少1个符合条件的元素 | 找到第1个符合条件的元素,立即返回true | false |
every() | 所有元素都符合条件 | 找到第1个不符合条件的元素,立即返回false | true |
你可以把这个表格存下来,碰到不确定的时候翻一翻,保证不会搞混。
这些坑我踩过,你别再踩了:细节决定对错
搞清楚逻辑还不够,有些细节不注意,一样会出问题。我把自己踩过的坑列出来,你别再踩了。
坑1:回调函数里的this
指向,一不小心就错了
你有没有试过在回调函数里用this
,结果发现this
不是你想要的对象?我之前写一个购物车功能时,想在every()
里判断所有商品都在库存中,代码是这样的:const isAllInStock = this.products.every(function(product) { return this.inventory.includes(product.id) })
。结果运行的时候,报错说this.inventory
是undefined
——因为默认情况下,回调函数的this
是全局对象(浏览器里是window
,Node.js里是global
),如果用了严格模式,就是undefined
。我当时用了严格模式,所以this
是undefined
,自然找不到inventory
。
后来我用了箭头函数,才解决这个问题——箭头函数不会改变this
的指向,会继承外层函数的this
。修改后的代码是:const isAllInStock = this.products.every(product => this.inventory.includes(product.id))
,这里的this
就指向购物车实例了,没问题。或者你也可以用bind()
方法,把this
绑定到回调函数上,比如const isAllInStock = this.products.every(function(product) { return this.inventory.includes(product.id) }.bind(this))
,但箭头函数更简洁,我更推荐用箭头函数。
坑2:忽略稀疏数组的“空槽”,结果判断不准
你知道吗?数组里的“空槽”(比如const arr = [1, , 3]
,中间的逗号就是空槽),some()
和every()
会怎么处理?我之前做一个数据清洗的任务时,碰到过——数组里有个空槽,用some()
判断有没有大于2的数,结果空槽被当成了undefined
,导致判断结果出错。
其实,稀疏数组的空槽在遍历的时候,会被当成undefined
,但some()
和every()
不会跳过空槽——它们会遍历所有索引,包括空槽。比如const arr = [1, , 3]
,arr.some(item => item > 2)
,遍历到第二个元素(空槽)时,item
是undefined
,undefined > 2
是false
,然后遍历第三个元素3,返回true
。而如果是arr.every(item => item > 0)
,第二个元素是undefined
,undefined > 0
是false
,所以返回false
。
如果你的数组可能有稀疏元素,怎么办?要么先把空槽填满,比如用arr.fill(0)
把空槽填满0;要么在回调函数里处理undefined
的情况,比如item !== undefined && item > 0
。比如arr.some(item => item !== undefined && item > 2)
,这样空槽就不会影响判断结果了。
坑3:短路求值的“副作用”,比如修改元素值
你有没有试过在回调函数里修改元素的值,结果因为短路求值,导致后面的元素没被修改?我之前做一个数据处理的任务时,想给数组里的每个元素加1,同时用some()
判断有没有大于10的元素。代码是这样的:const arr = [8, 9, 10]
,const hasGreaterThan10 = arr.some(item => { item += 1; return item > 10 })
。结果hasGreaterThan10
是true
,但数组变成了[9, 9, 10]
——因为some()
遍历到第一个元素8,加1变成9,不大于10,继续遍历第二个元素9,加1变成10,还不大于10,继续遍历第三个元素10,加1变成11,大于10,返回true
,后面的元素(没有了)不用处理。但如果数组是[8, 10, 9]
,some()
遍历到第二个元素10,加1变成11,返回true
,第三个元素9就不会被加1了,数组变成[9, 11, 9]
。
所以如果你在回调函数里做了有“副作用”的操作(比如修改元素值、发送请求、打印日志),一定要注意短路求值的影响——因为有些元素不会被遍历到,副作用不会发生在这些元素上。如果你的逻辑依赖于所有元素都被遍历,那最好不用some()
或every()
,改用forEach()
或者for
循环,因为它们会遍历所有元素。
这些技巧超好用,帮你省时间:把方法玩出花
吃透逻辑、避开坑之后,你可以试试这些技巧,让这两个方法更好用。
技巧1:和filter()
、map()
配合,实现复杂逻辑
你有没有试过把some()
或every()
和其他数组方法结合起来用?我之前做一个订单统计的功能时,想找出所有包含至少一个退货商品的订单,就用了filter()
加some()
:const returnOrders = orders.filter(order => order.products.some(product => product.isReturned))
。这里先过滤订单数组,每个订单检查它的商品数组里有没有退货的商品,用some()
判断,是不是很简洁?
再比如,我想判断所有商品的折扣后的价格都低于原价的80%,可以用every()
加map()
吗?其实不用map()
,直接在every()
里处理就行:const isAllDiscounted = products.every(product => product.discountedPrice < product.originalPrice * 0.8)
。这样更简洁,而且性能更好——因为不用额外生成一个map
后的数组。
还有,你可以用some()
和find()
配合,比如找到数组里第一个大于10的元素,用find()
是const found = arr.find(item => item > 10)
,而用some()
的话,可以这样写:let foundItem; arr.some(item => { if (item > 10) { foundItem = item; return true; } })
——不过find()
更直接,我还是推荐用find()
找元素,用some()
判断存在性。
技巧2:用some()
做“存在性检查”,比find()
更简洁
说到存在性检查,some()
比find()
更简洁——因为some()
直接返回布尔值,而find()
返回找到的元素(没找到返回undefined
)。比如你想知道数组里有没有苹果,用some()
是const hasApple = fruits.some(fruit => fruit === 'apple')
,直接得到true
或false
;而用find()
是const apple = fruits.find(fruit => fruit === 'apple')
,然后判断apple !== undefined
,是不是多了一步?所以存在性检查用some()
更方便。
我之前做一个搜索功能时,想判断搜索关键词有没有在商品名称里出现,就用了some()
:const isKeywordPresent = products.some(product => product.name.includes(keyword))
,直接得到结果,很方便。
技巧3:用every()
做“全选验证”,表单验证超方便
我现在做表单验证时,经常用every()
——比如验证所有必填输入框都不为空,代码是这样的:const formInputs = document.querySelectorAll('.required-input')
;const isAllFilled = [...formInputs].every(input => input.value.trim() !== '')
。这里用了扩展运算符把NodeList
转换成数组,然后every()
判断每个输入框的值去掉空格后都不为空。比一个个判断输入框方便多了,而且代码更简洁。
再比如,验证所有密码字符都符合要求(比如同时有数字和字母),可以用some()
判断有数字,再用some()
判断有字母,然后两个结果都为true
:const password = document.querySelector('#password').value
;const hasNumber = password.split('').some(char => !isNaN(char))
;const hasLetter = password.split('').some(char => /[a-zA-Z]/.test(char))
;const isPasswordValid = hasNumber && hasLetter
。这样是不是很清晰?
还有,验证所有 checkbox 都被选中,用every()
也很方便:const checkboxes = document.querySelectorAll('.agree-checkbox')
;const isAllChecked = [...checkboxes].every(checkbox => checkbox.checked)
,直接得到结果。
我之前用这些技巧优化了三个项目的代码,把原来的循环判断改成了some()
和every()
,代码行数少了三分之一,可读性还提高了——同事看我的代码时,都说“这段写得真清楚”。如果你按这些方法试了,或者碰到了什么问题,欢迎回来告诉我——我帮你一起琢磨琢磨!
其实some()和every()的区别,你用生活里的小事一类比就门儿清。比如你早上急着出门找充电宝,翻了床头柜抽屉、背包外层、沙发缝这三个地方——只要在沙发缝里摸到充电宝,是不是立马就停手,不用再翻其他地方了?some()就跟这事儿一样,它要的是“数组里有没有至少一个元素符合条件”,只要找到第一个符合的,直接返回true,剩下的元素根本不碰。就像找充电宝,找到一个就够了,没必要把家里翻个底朝天。
那every()呢?更像你检查孩子的周末作业。比如孩子做了10道数学题,你得一道一道看——要是第3题算错了,是不是就不用再看后面7道了?直接说“这作业没全对”?对,every()的逻辑就是“所有元素都得符合条件”,只要碰到第一个不符合的,立刻停止遍历,返回false。比如你有个成绩数组[80,75,65,58],用every()查“是不是所有都及格(≥60)”,查到58的时候,就知道有不及格的,直接返回false,后面的元素不用管了。
再往细了说,你要是碰到全及格的数组[92,88,79],用some()查“有没有不及格的”,得把整个数组遍历完,确认没有符合条件的元素,才会返回false;而用every()查“是不是全及格”,遍历完所有元素都符合,才会返回true。你看,两者的逻辑刚好是“反向”的——some()是“找存在”,只要有一个就成;every()是“查全部”,少一个都不行。
还有个细节你肯定碰到过:比如数组里有“空槽”(比如[1,,3]),some()和every()都会把空槽当undefined处理。比如你用some()查“有没有大于2的元素”,空槽的undefined不满足,但遍历到3的时候还是会返回true;可要是用every()查“是不是所有元素都大于0”,空槽的undefined就会让结果变成false——这也是因为every()要求“所有都符合”,哪怕一个空槽都不行。
其实本质上,这俩方法都是“短路求值”的设计,目的是省性能——能少遍历一个元素就少遍历一个。但你得先把逻辑掰扯明白:要“找一个符合”就用some(),要“查全符合”就用every(),千万别搞反了——我之前帮同事调bug,就碰到过把every()写成some()的情况,结果表单验证“只要有一个输入对就通过”,差点让错误数据进数据库,亏得及时发现了。
some()和every()的核心区别是什么?
some()的核心是“存在至少一个符合条件的元素”——只要数组里有一个元素满足条件,就返回true;every()的核心是“所有元素都符合条件”——只有数组里所有元素都满足条件,才返回true。简单 some()是“找有一个对的”,every()是“查全对”。
空数组调用some()或every()会返回什么?
根据JavaScript语言规范,空数组调用some()会返回false(因为没有元素能满足“存在至少一个”的条件);调用every()会返回true(因为没有元素违反“所有都满足”的条件)。如果你的数组可能为空, 先判断数组长度(比如if (arr.length > 0))再调用方法,避免逻辑错误。
some()和every()的“短路求值”会影响遍历吗?
会,而且是刻意设计的性能优化。some()遍历数组时,一旦找到第一个符合条件的元素,就会立即停止后续遍历,直接返回true;every()则是一旦找到第一个不符合条件的元素,就停止遍历,返回false。这种“短路”能减少不必要的计算,但如果回调函数里有“副作用”(比如修改元素值、发送请求),未遍历的元素不会执行这些操作。
为什么回调函数里的this有时候是undefined?
默认情况下,回调函数的this指向全局对象(浏览器中是window,Node.js中是global);如果代码用了严格模式(”use strict”),this会变成undefined。解决这个问题的常用方法是:用箭头函数(继承外层函数的this指向),或用bind()方法把this绑定到目标对象(比如fn.bind(this))。
稀疏数组里的空槽会影响some()/every()的判断吗?
会。稀疏数组的空槽(比如[1, ,3]中间的空位置)在遍历时会被当作undefined,some()和every()不会跳过这些空槽。 数组[1, ,3]调用some(item => item > 2)时,空槽的item是undefined(不满足条件),会继续遍历到3返回true;而调用every(item => item > 0)时,空槽的undefined会导致返回false。如果需要忽略空槽, 先填充数组(比如arr.fill(0))或在回调函数里过滤undefined(比如item !== undefined && …)。