小心这个陷阱: 为什么JS中的 every()对空数组总返回 true

更新日期: 2023-09-19阅读: 592标签: 数组

JavaScript 语言的核心部分足够大,以至于我们很容易误解其某些部分的工作方式。最近在重构一些使用 every() 方法的代码时,发现实际上并不理解其背后的逻辑。在我的理解中,我认为回调函数必须被调用并返回true , every() 才会返回 true ,但实际上并非如此。对于一个空数组, every() 无论回调函数是什么都会返回 true ,因为那个回调函数从未被调用过。考虑以下情况:

function isNumber(value) {
return typeof value === "number";
}

[1].every(isNumber); // true
["1"].every(isNumber); // false
[1, 2, 3].every(isNumber); // true
[1, "2", 3].every(isNumber); // false
[].every(isNumber); // true

在这个例子的每个情况中,对 every() 的调用都会检查数组中的每个项目是否为数字。前四次调用相当直接, every() 产生了预期的结果。现在考虑以下这些例子:

[].every(() => true);           // true
[].every(() => false); // true

这可能更令人惊讶:返回 true 或 false 的回调函数具有相同的结果。这只能发生的唯一原因是如果回调函数没有被调用,而 every() 的默认值是 true 。但是,为什么在没有值来运行回调函数时,空数组会返回 true 给 every() 呢?

要理解为什么,我们需要仔细看看规范是如何描述这个方法的。

实现 every()

ECMA-262 定义了一个 Array.prototype.every() 算法,大致可以翻译成这段JavaScript代码:

Array.prototype.every = function(callbackfn, thisArg) {

const O = this;
const len = O.length;

if (typeof callbackfn !== "function") {
throw new TypeError("Callback isn't callable");
}

let k = 0;

while (k < len) {
const Pk = String(k);
const kPresent = O.hasOwnProperty(Pk);

if (kPresent) {
const kValue = O[Pk];
const testResult = Boolean(callbackfn.call(thisArg, kValue, k, O));

if (testResult === false) {
return false;
}
}

k = k + 1;
}

return true;
};

从代码中,你可以看到 every() 假设结果是 true ,并且只有在回调函数对数组中的任何一项返回 false 时才返回 false 。如果数组中没有任何项目,那么就没有机会执行回调函数,因此,该方法无法返回 false 。

现在的问题是:为什么 every() 会表现出这样的行为?

在数学和JavaScript中的“对所有”的量词

MDN页面 提供了为什么 every() 会对空数组返回 true 的答案:

every 的行为就像数学中的“全称量词”。特别是对于空数组,它返回真值。(空集中的所有元素都满足任何给定条件,这是显然的真理。)

空真(Vacuous truth)是一个数学概念,意味着如果给定的条件(称为前件)不能被满足(即给定的条件不为真),那么某件事就是真的。用JavaScript的术语来说,every() 对于一个空集合返回 true,因为没有办法调用回调函数。回调函数代表要测试的条件,如果由于数组中没有值而无法执行它,那么 every() 必须返回 true。

“全称量词”("for all" quantifier)是数学中更大主题“全称量化”(universal quantification)的一部分,它允许你对数据集进行推理。考虑到JavaScript数组在进行数学计算方面的重要性,尤其是在使用类型数组(typed arrays)的情况下,内置支持这样的操作是合理的。而**every()**方法并不是唯一的例子。

在数学和JavaScript中的“存在量词”

JavaScript的 some() 方法实现了存在量化(existential quantification)中的“存在量词”(“存在”有时也被称为“存在”或“对某些”)。这个“存在量词”规定,对于任何空集合,结果都是假的。因此,some() 方法对空集合返回 false,并且也不会执行回调函数。以下是一些相关的示例:

function isNumber(value) {
return typeof value === "number";
}

[1].some(isNumber); // true
["1"].some(isNumber); // false
[1, 2, 3].some(isNumber); // true
[1, "2", 3].some(isNumber); // true
[].some(isNumber); // false
[].some(() => true); // false
[].some(() => false); // false

其他语言中的量化

JavaScript并不是唯一实现了集合或可迭代对象的量化方法的编程语言:

  • Python: all() 函数实现了“对所有” ,而 any() 函数实现了“存在” 。
  • Rust: Iterator::all() 方法实现了“对所有” ,而 any() 函数实现了“存在” 。

“全称量词”(for all)的 every() 方法的含义与影响

无论你是否认为 every() 方法的行为违反直觉都是可以讨论的。然而,无论你的观点如何,你都需要了解 every() 的“全称量词”(for all)特性以避免错误。简而言之,如果你使用 every() 方法或可能为空的数组,你应该事先进行明确的检查。例如,如果你有一个依赖于数字数组的操作,并且在数组为空时会失败,那么在使用 every() 之前,你应该检查数组是否为空。

function doSomethingWithNumbers(numbers) {

// first check the length
if (numbers.length === 0) {
throw new TypeError("Numbers array is empty; this method requires at least one number.");
}

// now check with every()
if (numbers.every(isNumber)) {
operationRequiringNonEmptyArray(numbers);
}

}

再次强调,只有当你有一个数组在为空时不应该被用于操作时,这才重要;否则,你可以避免这个额外的检查。

结论

当我第一次看到 every() 在空数组上的行为时,我感到很惊讶,但一旦你理解了这个操作的更大背景和这个功能在各种语言中的广泛应用,就会觉得它是有道理的。如果你也对这个行为感到困惑,那么我建议你改变阅读 every() 调用的方式。不要把 every() 理解为“这个数组中的每一项是否都符合这个条件?”而应该理解为“这个数组中是否有任何一项不符合这个条件?”这种思维方式的转变可以帮助你避免在未来的JavaScript代码中出现错误。

来源: 大迁世界


链接: https://www.fly63.com/article/detial/12518

探索JavaScript数组奥秘

avaScript数组同后端语言一样,具有它自己的数据结构,归根结底,这种数据结构,本质就是一种集合。在后端语言中(如java,.net等),数组是这样定义的:数组是用来存储相同数据类型的集合

js使用数组+循环+条件实现数字转换为汉字的简单方法。

单个数字转汉字的解决方法:利用数组存储0-9的汉字、 ary.length和str.length不用多说,这是指ary数组和str字符串的长度。这里我们需要注意的是str.charAt(j)和ary[i],分别指在str这个字符串中索引为j的元素,在ary中索引为i的元素。

[译]async-await 数组循环的几个坑

在 Javascript 循环中使用 async/ await 循环遍历数组似乎很简单,但是在将两者结合使用时需要注意一些非直观的行为。让我们看看三个不同的例子,看看你应该注意什么,以及哪个循环最适合特定用例。

数组、字符串去重

今天说的数组和字符串去重呢,主要用到es6新的数据结构 Set,它类似于数组,但是成员的值都是唯一的,没有重复的值,所以活用Set来进行数组和字符串的去重。

JavaScript 数组方法

数组方法:1、Array.join([param]) 方法:将数组中所有的元素都转换为字符串并连接起来,通过字符 param 连接,默认使用逗号,返回最后生成的字符串2、Array.reverse() 方法:将数组中的元素颠倒顺序(在原数组中重新排列它们),返回逆序数组

如何删除JavaScript 数组中的虚值

falsy(虚值)是在 Boolean 上下文中已认定可转换为‘假‘的值.JavaScript 在需要用到布尔类型值的上下文中使用强制类型转换(Type Conversion )将值转换为布尔值,比如:在条件语句或者循环语句中。

JavaScript中十种一步拷贝数组的方法

JavaScript中我们经常会遇到拷贝数组的场景,但是都有哪些方式能够来实现呢,我们不妨来梳理一下。扩展运算符(浅拷贝)自从ES6出现以来,这已经成为最流行的方法。

JS数组的几个经典api

本文主要来讲数组api的一些操作,如简单实现扁平化n维数组、数组去重、求数组最大值、数组求和、排序、对象和数组的转化等。扁平化嵌套数组/展平和阵列孔——flat()

关于Vue不能监听(watch)数组变化

vue无法监听数组变化的情况,但是数组在下面两种情况下无法监听:利用索引直接设置数组项时,例如arr[indexofitem]=newValue;修改数组的长度时,例如arr.length=newLength

JS计算两个数组的交集、差集、并集、补集(多种实现方式)

使用 ES5 语法来实现虽然会麻烦些,但兼容性最好,不用考虑浏览器 JavaScript 版本,使用 ES5 语法来实现虽然会麻烦些,但兼容性最好,不用考虑浏览器 JavaScript 版本。也不用引入其他第三方库。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!