如何使用.every()和.some()操作JavaScript数组

来自菜鸟教程
跳转至:导航、​搜索

介绍

ES5 和 ES6 为 JavaScript 带来了许多变化,包括更好地处理以数组表示的数据集合。 虽然该语言显着改进了对数组声明性数据操作的支持,并且许多现代浏览器都支持它们,但通常只使用有限的数组功能子集。 特别是,.every().some() 函数可以改进开发人员操作数组的方式,并可能给他们带来性能提升。

本文将展示主要的 JavaScript 数组函数是 .map().filter().reduce(),然后将介绍 .every().some() 将比更突出的解决方案节省计算能力。

.map().filter().reduce():著名的 JavaScript 数组函数

.map().filter().reduce() 的用法很常见。 出于本文的目的,我们将这三个函数称为 MFR

这些函数比其他用于在 JavaScript 中处理数组数据的函数更加突出。 例如,Google 趋势 显示对 MFR 的活跃搜索比其他函数的样本多:

Google 搜索 还表明,对 MFR 的搜索查询会产生更多结果。

.filter() 的搜索结果高达 74M 以上。 这比 .every() 的结果高 99.97% hi,比 .some() 的结果高 99.98% hi。 这意味着人们正在谈论、写作和教授更多 MFR,这表明随着时间的推移会有更多的使用。

.every().some() 的优点

在 JavaScript 中,通常教导将数组的计算限制为迭代(.forEach)和转换(.map、.filter、.reduce - aka MFR)操作。 这可能导致可以使用 .some().every() 完成的计算被强制进入 MFR ,尤其是 .filter() 后跟 .[ 的序列X162X]。

几乎所有需要提前退出而不是梳理整个集合的 MFR 用法都是 .some().every() 的主要候选者,因为它们都短路和尽早退出数组迭代,而不是运行到最后并可能浪费计算资源。

在我们构建更复杂的应用程序并向客户端发送更多字节的时候,导致解析 JavaScript 的大量开销(参见 TTI,我们可以做任何事情来进行三个迭代(由于短路)而不是三十是非常受欢迎的。

Array.prototype.every()

every() 方法允许我们确定数组中的 每个 元素是否满足特定要求。 它在第一次找到不满足给定要求的元素时停止评估阵列(短路)。

Array.prototype.some()

some() 方法允许我们确定数组中的 some(至少一个)元素是否满足特定要求。 它在第一次找到满足给定要求的元素时停止评估阵列(短路)。

一个例子,两个场景

假设您被要求编写一个简单的 add() 函数来添加一堆整数。 该函数应该期望被赋予任意数量的要相加的整数,并且它应该在计算加法后返回它们的总和。

以下是完成此任务的两种方法。

情景一

如果给 add() 函数提供混合输入(例如,整数、浮点数、未定义、字符串等),它应该只处理整数并返回添加它们的总和。 我们可以这样实现我们的 add() 函数:

const add = (...entries) => {
  return entries
    .filter(Number.isInteger)
    .reduce((sum, int) => {
      return sum + int;
    }, 0);
};

由于这种情况需要 add() 函数从给定输入中存在的任何整数计算总和,因此可以像我们在前面的代码块中那样实现该函数。 我们首先使用 .filter() 仅提取整数,然后使用 .reduce() 计算总和。 然而,即使其中没有整数,.filter() 操作也会遍历整个数组,从而浪费计算资源。

人们也可以争辩说我们可以得到这样一个更好的解决方案:

const add = (...entries) => {
  return entries.reduce((sum, entry) => {
    if(Number.isInteger(entry)) {
      return sum + entry;
    }
    return sum;
  }, 0);
};

这种对 add() 的处理要好一些,因为与第一个迭代识别有效数据然后进一步迭代以基于有效数据计算结果的第一个不同,现在只有一个迭代块。 但是,即使其中可能没有整数,我们仍然会一直运行到数组的末尾。

我们需要一种方法来首先判断收集的输入是否至少有一个整数。 由于我们试图从输入中找到总和,因此实际上我们至少需要两个整数。 如果我们能找到两个或更多整数,我们将继续将输入减少为整数之和。

让我们使用 .some() 确保满足这个条件:

const add = (...entries) => {
  let theSum = 0;
  if(hasTwoOrMoreInts(entries)){
    // there are >= 2 integers, lets sum them
    theSum = entries.reduce((sum, entry) => {
      if(Number.isInteger(entry)) {
        return sum + entry;
      }
      return sum;
    }, 0);
  }
  return theSum;
};

现在我们有一个条件阻止求和计算,除非我们确定有两个或更多整数。 我们使用 hasTwoOrMoreInts() 函数执行此操作,我们将在下面创建该函数:

const hasTwoOrMoreInts = (entries) => {
  let lastIndex = -1;
  let hasMinimumIntsCount = false;

  const hasAnInt = entries.some((entry, index) => {
    lastIndex = index;
    return Number.isInteger(entry);
  });

  if(hasAnInt === true) {
    // we've got one int, is there another?
    const hasMoreInts = entries.slice(lastIndex + 1).some(Number.isInteger);
    hasMinimumIntsCount = (hasMoreInts === true) && hasAnInt;
  }

  return hasMinimumIntsCount;
};

方案二

如果 add() 函数可以接收混合输入(例如整数、浮点数、未定义、字符串等),但如果所有输入都是整数,则只需要继续计算给定输入的总和,这是一种常见的方法受 MFR 突出的影响可能如下所示:

const add = (...entries) => {
  let theSum = 0;
  const nonInts = entries.filter(entry => !Number.isInteger(entry));
  if(nonInts.length === 0) {  // are there non-ints?
    theSum = entries.reduce((sum, int) => {
      return sum + int;
    }, 0);
  }
  return theSum;
}

entries.filter() 再次尝试通过迭代整个 entries 数组来收集每个不是整数的输入来查看是否存在无效输入。 如果没有无效输入 (nonInts.length === 0),我们用 .reduce() 计算总和。 如果我们需要所有无效输入的总数或所有输入本身,这将是有意义的。 否则,我们应该寻找第一个无效输入并从那里决定下一步该做什么。

.some() 一样,.every() 将根据最少的必要信息进行操作。 在这种情况下,一旦它找到一个不是整数的输入,它就不会再看下去(通过返回 false 退出)并允许我们继续下一个重要的事情。

让我们看看 .every() 如何为我们做到这一点:

const add = (...entries) => {
  let theSum = 0;
  const areAllInts = entries.every(Number.isInteger);
  if(areAllInts === true) {  // are these indeed all ints?
    theSum = entries.reduce((sum, int) => {
      return sum + int;
    }, 0);
  }
  return theSum;
};

由于 entries.every() 将在找到不是整数后立即返回 false,因此我们能够退出对 entries 中无效元素的进一步测试,从而释放可能需要为移动用户提供流畅的滚动体验。

一个更实际的例子

程序员不会经常编写 add() 函数,所以让我们看一个真实的例子:将 .every().some() 应用于假设的 HTML 表单验证.

我们希望仅在从表单中获取并验证了所有必需的数据后才提交表单。 我们同样希望至少填写一个可选数据:

const requiredFields = Array.of(
  isFirstNameValid(),
  isLastNameValid(),
  isEmailValid(),
  isAboutMeValid()
);

const optionalFields = Array.of(
  isTwitterValueValid(),
  isFacebookValue(),
  isGoogleplusValueValue()
);

const isValid = (inputStatus) => inputStatus === true;

if(requiredFields.every(isValid) && optionalFields.some(isValid)) {
  // all required fields are valid
  // and at least one social media field is valid
  // lets proceed to submit the form now
} else {
    // lets tell the user we are serious here
    // this will happen really fast since we are short-circuiting 
    // with .some and .every above
}

如上所示,Array.of() 中的所有函数都在对特定的表单字段进行验证,然后返回 truefalse。 由于 Array.of() 是根据给定参数(在我们的例子中是验证函数)构造数组的工厂,这意味着 optionalFields 最终可能看起来像 [true, false, false]。 这样,如果 requiredFields 中的任何值是假的,或者如果 optionalFields 都不为真,我们不会提交表单。 表单提交由最短的执行路径决定。

结论

这种探索有助于揭示 MFR 的重要性。 它还阐明了为什么 .every().some() 可以为您提供更有效的数组操作策略。

以下是在您的 Javascript 代码库中推广或采用 .some().every() 而不是 MFR 的一些可能方案:

  • .filter() 紧跟 .forEach().map().reduce() 或任何涉及它们的链式组合。
  • 一个 .filter() 紧跟一个检查调用结果的条件,并且条件主体包含 .forEach().map().reduce() 或任何涉及它们的链式组合,作用于 .filter() 的结果。

对于这些和其他相关场景,尽快退出检查将优化您的资源。