JavaScript 小惊喜:对于空数组every()方法居然返回true

更新日期: 2019-08-07阅读: 1.4k标签: 数组

前言

JavaScript 语言的内核足够大,导致我们很容易误解它的某些部分是如何工作的。我最近重构了一些使用 every ()方法的代码,并且发现我并不真正理解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(() => true);           // true
[].every(() => false);          // true

这样的结果可能更令人感到惊讶: 对于every(),返回 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 中的全称量词

译者注:全称量词是指“所有”的概念,常用符号为∀。例如,对于集合S中的元素x,可以表示为∀x∈S,意为“对于S中的每一个元素x都成立”

MDN 提供了为什么 every ()对于空数组返回 true 的答案:

every 和数学中的全称量词"任意(∀)"类似。特别的,对于空数组,它只返回 true。(这种情况属于无条件正确,因为空集的所有元素都符合给定的条件。)

无条件正确是一个数学概念,它意味着如果一个给定的条件(称为先行条件)不能被满足(也就是说,给定的条件是不真实的) ,那么某些东西就是真的。要将其应用到 JavaScript 中,那就是every ()对于空数组返回 true,因为没有办法调用回调函数 。回调代表了要测试的条件,如果由于数组中没有值而无法执行,那么 every ()必须返回 true。

全称量词是数学中一个更大的主题的一部分,这个主题被称为“全称量化”,它允许你对数据集进行推理。考虑到 JavaScript 数组对于执行数学计算的重要性,特别是对于类型化数组,内置支持这种操作是有意义的。要知道,every()并不是唯一的例子。


数学和 JavaScript 中的存在量词

译者注: 存在量词是指“存在”的概念,常用符号为∃。例如,对于集合S中的元素x,可以表示为∃x∈S,意为“存在S中的一个元素x”

JavaScript 的some()方法实现了存在量词。“存在”量词指出,对于任何空集,结果都是 false。因此,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 ()函数实现了存在量词。

因此,JavaScript 与 every ()和 some ()都有着良好的合作关系。


意味着全称量词的every()

你是否认为evey()的行为是违反直觉的?这可有待商榷。然而,不管您的观点如何,您都需要了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 代码中避免错误。

作者:NewName
链接:https://juejin.cn/post/7279093851000062010

链接: https://fly63.com/article/detial/4579

探索JavaScript数组奥秘

avaScript数组同后端语言一样,具有它自己的数据结构,归根结底,这种数据结构,本质就是一种集合。在后端语言中(如java,.net等),数组是这样定义的:数组是用来存储相同数据类型的集合

js使用数组+循环+条件实现数字转换为汉字的简单方法。

单个数字转汉字的解决方法:利用数组存储0-9的汉字、 ary.length和str.length不用多说,这是指ary数组和str字符串的长度。这里我们需要注意的是str.charAt(j)和ary[i],分别指在str这个字符串中索引为j的元素,在ary中索引为i的元素。

[译]async-await 数组循环的几个坑

在 Javascript 循环中使用 async/ await 循环遍历数组似乎很简单,但是在将两者结合使用时需要注意一些非直观的行为。让我们看看三个不同的例子,看看你应该注意什么,以及哪个循环最适合特定用例。

数组、字符串去重

今天说的数组和字符串去重呢,主要用到es6新的数据结构 Set,它类似于数组,但是成员的值都是唯一的,没有重复的值,所以活用Set来进行数组和字符串的去重。

JavaScript 数组方法

数组方法:1、Array.join([param]) 方法:将数组中所有的元素都转换为字符串并连接起来,通过字符 param 连接,默认使用逗号,返回最后生成的字符串2、Array.reverse() 方法:将数组中的元素颠倒顺序(在原数组中重新排列它们),返回逆序数组

如何删除JavaScript 数组中的虚值

falsy(虚值)是在 Boolean 上下文中已认定可转换为‘假‘的值.JavaScript 在需要用到布尔类型值的上下文中使用强制类型转换(Type Conversion )将值转换为布尔值,比如:在条件语句或者循环语句中。

JavaScript中十种一步拷贝数组的方法

JavaScript中我们经常会遇到拷贝数组的场景,但是都有哪些方式能够来实现呢,我们不妨来梳理一下。扩展运算符(浅拷贝)自从ES6出现以来,这已经成为最流行的方法。

JS数组的几个经典api

本文主要来讲数组api的一些操作,如简单实现扁平化n维数组、数组去重、求数组最大值、数组求和、排序、对象和数组的转化等。扁平化嵌套数组/展平和阵列孔——flat()

关于Vue不能监听(watch)数组变化

vue无法监听数组变化的情况,但是数组在下面两种情况下无法监听:利用索引直接设置数组项时,例如arr[indexofitem]=newValue;修改数组的长度时,例如arr.length=newLength

JS计算两个数组的交集、差集、并集、补集(多种实现方式)

使用 ES5 语法来实现虽然会麻烦些,但兼容性最好,不用考虑浏览器 JavaScript 版本,使用 ES5 语法来实现虽然会麻烦些,但兼容性最好,不用考虑浏览器 JavaScript 版本。也不用引入其他第三方库。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!