乐趣区

关于前端:小心这个陷阱-为什么JS中的-every对空数组总返回-true

首发于公众号 大迁世界,欢送关注。📝 每周 7 篇实用的前端文章 🛠️ 分享值得关注的开发工具 😜分享集体守业过程中的趣事

JavaScript 语言的外围局部足够大,以至于咱们很容易误会其某些局部的工作形式。最近在重构一些应用 every() 办法的代码时,发现实际上并不了解其背地的逻辑。在我的了解中,我认为回调函数必须被调用并返回trueevery() 才会返回 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。然而,为什么在没有值来运行回调函数时,空数组会返回 trueevery() 呢?

要了解为什么,咱们须要认真看看标准是如何形容这个办法的。

实现 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 代码中呈现谬误。

交换

首发于公众号 大迁世界,欢送关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑难?我来答复

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

退出移动版