【译】为什么 JS 空数组调用 every() 方法祂喵地会返回 true?

大家好,这里是大家的林语冰。

免责声明

本文属于是语冰的直男翻译了属于是,仅供粉丝参考,英文原味版请临幸 JavaScript WTF: Why does every() return true for empty arrays?

作者:Nicholas C. Zakas(《JS 高级程序设计》兼 ESLint 作者)

当没有任何值可以测试时,如何满足条件?

JS(JavaScript)语言的核心太大了,所以很容易错误解读它的某些工作机制。我最近重构了某些使用了 every() 方法的代码,结果发现我其实并不理解该方法背后的逻辑。在我看来,我假设回调函数必然会被调用并返回 true,然后才导致 every() 返回 true,但实际情况并非如此。对于空数组,every() 会返回 true,不管回调函数是什么,因为该回调函数永远不会被调用。考虑以下情况:

在此例子的每一种 case 中,every() 的调用都会检查数组中的每一项是否为一个数字。前四个调用一目了然,every() 产生了预期的结果。现在考虑这些例子:

这可能更加反直觉:返回 truefalse 的回调产生相同的结果。发生这种情况的真相只有一个,就是回调没有被调用,并且 every() 的默认值是 true。虽然但是,当没有值可以运行回调函数时,为什么空数组会让 every() 返回 true

要理解其中缘由,瞄一眼规范如何描述这个方法乃重中之重。

实现 every()

ECMA-262 定义了一个 Array.prototype.every() 算法,它大致可以转换为下述 JS 代码:

如你所见,every() 假设结果是 true,并且当且仅当数组中的任意元素调用回调函数都返回 false 时,它才返回 false。如果数组没有元素,那么就没有机会执行回调函数,因此,该方法无法返回 false

现在的问题是:为何如此设计 every() 的行为?

数学和 JS 中的全称量词("for all" quantifier)

关于空数组调用 every() 返回 true 的原因,MDN 网页 提供了答案:

every 的作用就像数学中的"for all"量词。特别是,对于空数组,它返回 true。(地球人都知道,空集的所有元素都满足任何给定的条件。)

空真(Vacuous truth) 是一个数学概念,它意味着若给定条件(称为前提)不能满足(比如给定条件不为真),则某事为真。用 JS 的术语来说,every() 返回 true,是因为它无法调用回调。回调表示要测试的条件,如果因为数组中没有值而无法执行,那么 every() 必须返回 true

全称量词 是数学中一个更大主题的一部分,它允许您对数据集进行推理。考虑到 JS 数组对于执行数学计算的重要性,尤其是类型化数组,为此类操作提供内置支持是有意义的。every() 不是孤例。

数学和 JS 中的存在量词("there exists"量词)

JS 的 some() 方法在 特称量词(存在量词有时也被称为特称量词)中实现了 存在量词。存在量词表示对于任何空集而言,结果都为 false。因此,对于一个空集,some() 方法返回 false,并且它也不执行回调。以下是一些例子(一语双关):

其他语言中的量词

JS 并不是唯一一种为集合或可迭代对象实现量词方法的编程语言:

因此,JS 与 every()some() 一拍即合。

every() 的全称量词含义

您是否认为 every() 的行为是反直觉的,这还有待商榷。虽然但是,抛开您的个人意见不谈,您需要意识到 every() 的全称量词本质,避免犯错。简而言之,如果您在使用 every() 或一个可能为空的数组,您应该事先添加一个显式的检查。举个栗子,如果您有一个依赖于数字数组的操作,并且会因为一个空数组而失败,那么您应该在使用 every() 之前检查数组是否为空:

同理可得,当且仅当您有不该被用于某操作的空数组时,这才显得重要;否则,您可以避免这种额外的检查。

结论

虽然我被空数组上 every() 的行为惊到,但一旦您理解了该操作更广义的上下文以及此功能的跨语言推广,那么您就会明白这别有深意。如果您也对这种行为感到困惑,那么我建议您在邂逅 every() 调用时改变解读它们的方式。将 every 解读为"此数组中是否存在某元素与此条件不匹配?",而不是解读为"此数组中的任一元素是否都匹配此条件?"。这种思维上的转变可以帮助您避免 JS 代码中的错误。

学废了的小伙伴可以点赞给语冰打 call,欢迎关注最新动态和订阅前沿资讯。谢谢大家的彼芯,掰掰~

相关推荐
司篂篂1 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客1 小时前
pinia在vue3中的使用
前端·javascript·vue.js
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
我码玄黄4 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
爱喝水的小鼠4 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学4 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
WeiShuai4 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
forwardMyLife4 小时前
element-plus的面包屑组件el-breadcrumb
javascript·vue.js·ecmascript
mez_Blog6 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
珊珊而川6 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试