JavaScript is required
Blog About

为什么 every() 对于空数组返回 true?

2024/01/26
7 mins read
See this issue
# Javascript
Back

原文地址: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

这可能更令人惊讶:返回 truefalse 的回调具有相同的结果。发生这种情况的唯一原因是回调未被调用并且 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 并不是唯一实现集合或可迭代量化方法的编程语言:

  • Pythonall() 函数实现“for all” 而 any() 函数实现“there isn’t” 。
  • RustIterator::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 代码中出现错误。