为什么 every() 对于空数组返回 true?
原文地址:JavaScript WTF: Why does every() return true for empty arrays?
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
。但是,当没有值可运行回调函数时,为什么空数组会为 every()
返回 true
呢?
要了解原因,重要的是要查看规范如何描述此方法。
# 实施 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 中的“for all”量词
MDN 页面 提供了为什么 every()
对于空数组返回 true
的答案:
every
的作用类似于数学中的“for all”量词。特别是,对于空数组,它返回 true。 (空集的所有元素满足任何给定条件是绝对正确的。)
空洞真理是一个数学概念,意味着如果给定条件(称为先行条件)无法满足(即给定条件不为真),则某件事为真。 要将其放回到 JavaScript 术语中, every()
对于空集返回 true
,因为无法调用回调。回调表示要测试的条件,如果由于数组中没有值而无法执行,则 every()
必须返回 true
。
“for all”量词是一个更大的数学主题的一部分,称为通用量化 ,它允许您对数据集进行推理。鉴于 JavaScript 数组对于执行数学计算的重要性,尤其是类型化数组,因此内置对此类操作的支持是有意义的。 every()
并不是唯一的例子。
# 数学和 JavaScript 中的“存在”量词
JavaScript some()
方法从存在量化 实现“存在”量词 (“存在”有时也称为“存在”或“对于某些” )。 “存在”量词表明对于任何空集结果都是假的。因此, 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()
函数实现“for all” 而any()
函数实现“there isn’t” 。 - Rust:
Iterator::all()
方法实现“for all” 而any()
函数实现“there isn’t” 。
所以 JavaScript 与 every()
和 some()
是很好的合作伙伴。
# every()的“for all ”含义
您是否认为 every()
的行为违反直觉,还有待讨论 。然而,无论您的观点如何,您都需要了解 every()
的“for all”性质,以避免错误。简而言之,如果您使用 every()
或可能为空的数组,则应事先添加显式检查。例如,如果您有一个依赖于数字数组的操作,并且会因空数组而失败,那么您应该在使用 every()
之前检查数组是否为空:
function doSomethingWithNumbers(numbers) {
// 首先检查长度
if (numbers.length === 0) {
throw new TypeError("Numbers array is empty; this method requires at least one number.");
}
// 现在检查every()
if (numbers.every(isNumber)) {
operationRequiringNonEmptyArray(numbers);
}
}
同样,只有当您有一个数组为空时不应用于操作时,这一点才重要;否则,您可以避免此额外检查。
# 结论
虽然我对空数组上 every()
的行为感到惊讶,但一旦您了解了该操作的更大上下文以及此功能跨语言的扩散,它就有意义了。如果您也对这种行为感到困惑,那么我建议您在遇到 every()
呼叫时改变阅读它们的方式。不要将 every()
读作“此数组中的每个项目都符合此条件吗?”读作“这个数组中是否有任何项目与这个条件不匹配?”这种思维转变有助于避免 JavaScript 代码中出现错误。