编程技术文章分享与教程

网站首页 > 技术文章 正文

为什么Array.every() 对于空数组总是返回 true?

hmc789 2024-11-10 10:38:13 技术文章 2 ℃

今天我们从源头探寻一下 Array.prototype.every 这个方法,便于正确理解这个知识点。

疑问点(懵逼的地方)

这个执行结果,看着有些和我们预想的结果不一样,期待的是false,所以疑问就产生了。

开始分析

MDN 这样描述:

every() 方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。

const isBelowThreshold = (currentValue) => currentValue < 40;
const array1 = [1, 30, 39, 29, 10, 13];
console.log(array1.every(isBelowThreshold));
// Expected output: true

看了 MDN 的描述后,你是不是依然懵逼中; 其实它的描述不是那么准确,哈哈

我们来看看 ecma262 标准是怎么解释的:

23.1.3.6 Array.prototype.every ( callbackfn [ , thisArg ] )

callbackfn should be a function that accepts three arguments and returns a value that is coercible to a Boolean value. every calls callbackfn once for each element present in the array, in ascending order, until it finds one where callbackfn returns false. If such an element is found, every immediately returns false. Otherwise, every returns true. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.

If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.

callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.

every does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.

The range of elements processed by every is set before the first call to callbackfn. Elements which are appended to the array after the call to every begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time every visits them; elements that are deleted after the call to every begins and before being visited are not visited. every acts like the "for all" quantifier in mathematics. In particular, for an empty array, it returns true.

This method performs the following steps when called:

  1. Let O be ? ToObject(this value).
  2. Let len be ? LengthOfArrayLike(O).
  3. If IsCallable(callbackfn) is false, throw a TypeError exception.
  4. Let k be 0.
  5. Repeat, while k < len, a. Let Pk be ! ToString((k)). b. Let kPresent be ? HasProperty(O, Pk). c. If kPresent is true, then i. Let kValue be ? Get(O, Pk). ii. Let testResult be ToBoolean(? Call(callbackfn, thisArg, ? kValue, (k), O ?)). iii. If testResult is false, return false. d. Set k to k + 1.
  6. Return true.

翻译一下,大概意思是:

callbackfn 应该是一个接受三个参数并返回一个可转换为布尔值的函数。every 会对数组中的每个元素(按升序)调用 callbackfn,直到找到一个使 callbackfn 返回 false 的元素。如果找到了这样的元素,every 会立即返回 false。否则,every 返回 truecallbackfn 只对数组中实际存在的元素调用,不会对缺失的元素调用。

如果提供了 thisArg 参数,它将作为每次调用 callbackfn 时的 this 值。如果未提供,则使用 undefined

调用 callbackfn 时会传递三个参数:元素的值、元素的索引和被遍历的对象。

every 方法不会直接修改调用它的对象,但对象可能会被调用 callbackfn 时的操作修改。

every 方法在第一次调用 callbackfn 之前就已经确定了处理的元素范围。追加到数组中的元素在 every 开始调用后不会被 callbackfn 访问。如果数组中的现有元素在 every 开始调用后被更改,则传递给 callbackfn 的值将是 every 访问它们时的值;在调用 every 之后但在访问之前被删除的元素不会被访问。every 类似于数学中的“全称量化”运算符。特别是对于一个空数组,它返回 true

相信当你看到这里时,已经解答了你心中的疑惑了吧。这就是规范里的官方解释!

什么,你说你还是懵逼的,觉得这不够直观,好的,明白 ,安排(我们直接上代码,给你最直观的感受,这总行了吧!)

我们现在就把 every 方法在调用时执行的步骤(就是上面没有翻译的那6点),用 JS 来模拟写出来(便于我们直接理解学习),因为在 V8 里的 every方法的源码是用 c++ 实现的。话不多说,开搞:

Array.prototype.myEvery = function(callbackfn, thisArg) {
    // 1. 将 this 值转换为对象。
    if (this == null) {
        throw new TypeError('Array.prototype.myEvery called on null or undefined');
    }
    const O = Object(this);

    // 2. 获取数组的长度。
    const len = O.length >>> 0;

    // 3. 如果 callbackfn 不是函数,则抛出 TypeError 异常。
    if (typeof callbackfn !== 'function') {
        throw new TypeError(callbackfn + ' is not a function');
    }

    // 4. 初始化索引 k 为 0。
    let k = 0;

    // 5. 当 k 小于 len 时,重复以下步骤:
    while (k < len) {
        // a. 将 k 转换为字符串 Pk。
        const Pk = k.toString();

        // b. 检查 O 是否具有属性 Pk。
        const kPresent = Pk in O;

        // c. 如果属性存在:
        if (kPresent) {
            // i. 获取属性值 kValue。
            const kValue = O[Pk];

            // ii. 使用 callbackfn 调用该值,并将结果转换为布尔值。
            const testResult = Boolean(callbackfn.call(thisArg, kValue, k, O));

            // iii. 如果结果为 false,立即返回 false。
            if (!testResult) {
                return false;
            }
        }
        // d. 增加 k。
        k++;
    }

    // 6. 如果所有元素都满足条件,返回 true。
    return true;
};

// 测试示例

const data1 = [1, 2, 3, 4].myEvery(num => num > 0); // 应该返回 true
const data2 = [1, 2, 3, -1].myEvery(num => num > 0); // 应该返回 false
const data3 = [].myEvery(num => num > 0); // 应该返回 true

console.log(data1); // true
console.log(data2); // false
console.log(data3); // true

这下够直观了吧。在上面 的代码里可以看见:当数组的长度为 0 时,就不会循环执行第 5 点,直接返回 true 了

最后

有什么观点或者疑问,欢迎在评论区留言,我们一起交流学习 !

Tags:

标签列表
最新留言