Array.every()可能引发的线上问题

53 阅读3分钟

Array.every()这个方法大家用过吗?

那看看下面这段代码会输出什么?

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 具有相同结果的回调。发生这种情况的唯一原因是,如果未调用回调,并且默认值为 true 。但是, every() 当没有值可以运行回调函数时,为什么会返回 true 一个空数组呢?

要了解原因,请务必查看规范如何描述此方法。

ECMA定义

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() 这种行为?

MDN

MDN 页面也特别提到了 every() 针对空数组返回true的答案:

developer.mozilla.org/zh-CN/docs/…

Array.some

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

every的含义

你是否认为这种行为 every() 是违反直觉的,还有待商榷。但是,无论你的意见如何,你都需要意识到“所有”的性质 every() ,以避免错误。简而言之,如果你使用的 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 代码中的错误。