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

介绍

ES5 和 ES6 对 JavaScript 带来了许多变化,包括更好的方法来处理以数组表达的数据集.虽然该语言对数组上的声明性数据操纵的支持得到了显著提高,许多现代浏览器支持它们,但只使用了有限的数组功能。

本文将展示显著的JavaScript数组函数为.map(),.filter().reduce(),然后将通过例子,其中.every().some()将节省计算功率在更显著的解决方案。

.map().filter().reduce(): 主要的 JavaScript 数组函数

使用 .map().filter().reduce() 是非常常见的,为本文的目的,让我们把这三个函数称为 MFR

这些函数比JavaScript中的其他操作数组数据的函数更为突出。 Google Trends例如,显示了对 MFR的更多活跃搜索,而不是其他函数的样本:

Google trends comparison of <code>.foreach</code>, <code>.filter</code>, <code>.map</code>, <code>.reduce</code>, and <code>.some</code> search frequency. <code>.filter</code>, <code>.map</code>, and <code>.reduce</code> are the highest.

Google Search还显示,对 MFR的搜索查询会产生更多的结果。

Bar graph comparing <code>.map</code>, <code>.filter</code>, <code>.reduce</code>, <code>.every</code>, and <code>.some</code> in terms of number of Google searches. <code>.map</code>, <code>.filter</code>, and <code>.reduce</code> are the highest by a large margin.

搜索结果为 .filter() 超过 74M 这比结果为 .every() 高 99.97%,比结果为 some() 高 99.98% 这意味着人们正在说话,写作和教学更多 MFR,这表明随着时间的推移使用量增加。

.every() 和.some() 的优点

在JavaScript中,它通常被教导将数组的计算限制在迭代(.forEach)和转换(.map,.filter,.reduce - a.k.a MFR)操作中。

几乎任何使用 MFR,在那里你需要尽早退出而不通过整个集合,都是对 .some().every() 的主要候选人,因为它们既缩短电路,也尽早退出数组迭代,而不是运行到尽头,并可能浪费计算资源。

在我们正在构建更复杂的应用程序并向客户端发送更多字节的时代,这导致了显著的JavaScript分析过剩(参见TTI),任何我们可以做的三次迭代(由于短路)而不是三十是非常受欢迎的。

「Array.prototype.every()」

every()方法允许我们确定一个数组中的每一个元素是否满足一定的要求,它在第一次发现一个不满足该要求的元素时停止对数组(短路)进行评估。

「Array.prototype.some()」

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

一个例子,两个场景

假设您被要求写一个简单的 add() 函数来添加一堆整数. 该函数应该预期会给出任何要添加的整数,并在计算添加后返回其总数。

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

剧本一

如果 add() 函数被给予混合输入(例如,整数,浮动,未定义,字符串等),它应该只使用整数来返回添加它们的总和 我们可以这样实现 add() 函数:

1const add = (...entries) => {
2  return entries
3    .filter(Number.isInteger)
4    .reduce((sum, int) => {
5      return sum + int;
6    }, 0);
7};

由于这个场景预计add()函数将从给定的输入中存在的任何整数计算出总和,所以我们可以像我们在上一个代码块中一样执行函数,我们首先使用.filter()来提取只有整数,然后用.reduce()计算总和。

还可以说,我们可以得到这样的更好的解决方案:

1const add = (...entries) => {
2  return entries.reduce((sum, entry) => {
3    if(Number.isInteger(entry)) {
4      return sum + entry;
5    }
6    return sum;
7  }, 0);
8};

这个add()的选择有点好,因为,与第一个重复来识别有效的数据,然后再重复来计算基于有效的数据的结果不同,现在只有一个重复块。

我们需要一种方法,首先知道收集的输入是否至少有一个整数. 由于我们正在尝试从输入中找到总数,我们实际上需要至少有两个整数。

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

 1const add = (...entries) => {
 2  let theSum = 0;
 3  if(hasTwoOrMoreInts(entries)){
 4    // there are >= 2 integers, lets sum them
 5    theSum = entries.reduce((sum, entry) => {
 6      if(Number.isInteger(entry)) {
 7        return sum + entry;
 8      }
 9      return sum;
10    }, 0);
11  }
12  return theSum;
13};

现在我们有一个条件,它阻止了总数的计算,除非我们确信有两个或多个整数。

 1const hasTwoOrMoreInts = (entries) => {
 2  let lastIndex = -1;
 3  let hasMinimumIntsCount = false;
 4
 5  const hasAnInt = entries.some((entry, index) => {
 6    lastIndex = index;
 7    return Number.isInteger(entry);
 8  });
 9
10  if(hasAnInt === true) {
11    // we've got one int, is there another?
12    const hasMoreInts = entries.slice(lastIndex + 1).some(Number.isInteger);
13    hasMinimumIntsCount = (hasMoreInts === true) && hasAnInt;
14  }
15
16  return hasMinimumIntsCount;
17};

剧本2

如果add()函数可以接收混合输入(如整数、浮数、未定义、字符串等),但只需要继续计算给定的输入的总和,如果所有输入都是整数,那么由MFR的突出性影响的一种常见方法可能看起来像这样:

 1const add = (...entries) => {
 2  let theSum = 0;
 3  const nonInts = entries.filter(entry => !Number.isInteger(entry));
 4  if(nonInts.length === 0) {  // are there non-ints?
 5    theSum = entries.reduce((sum, int) => {
 6      return sum + int;
 7    }, 0);
 8  }
 9  return theSum;
10}

再一次,‘entries.filter()’试图通过重复整个‘entries’数组来查看是否有无效的输入,以收集每个输入而不是整数。如果没有无效的输入(‘nonInts.length === 0’),我们用‘.reduce()’计算了总和。

在这种情况下,一旦找到一个不是整数的输入,它就不会再看得出来(通过退出返回false)并允许我们继续进行下一个重要的事情。

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

 1const add = (...entries) => {
 2  let theSum = 0;
 3  const areAllInts = entries.every(Number.isInteger);
 4  if(areAllInts === true) {  // are these indeed all ints?
 5    theSum = entries.reduce((sum, int) => {
 6      return sum + int;
 7    }, 0);
 8  }
 9  return theSum;
10};

由于entries.every()会返回false,一旦发现不是整数,我们可以对entries中的无效元素进行进一步测试,从而释放可能需要为移动用户提供流畅滚动体验的资源。

一个更实际的例子

程序员通常不会每天写add()函数,所以让我们看看一个现实世界的例子:将.every().some()应用到假设的HTML形式验证中。

我们希望在从表单中获取并验证所有所需数据后才提交表单,我们也希望至少填写一份可选数据:

 1const requiredFields = Array.of(
 2  isFirstNameValid(),
 3  isLastNameValid(),
 4  isEmailValid(),
 5  isAboutMeValid()
 6);
 7
 8const optionalFields = Array.of(
 9  isTwitterValueValid(),
10  isFacebookValue(),
11  isGoogleplusValueValue()
12);
13
14const isValid = (inputStatus) => inputStatus === true;
15
16if(requiredFields.every(isValid) && optionalFields.some(isValid)) {
17  // all required fields are valid
18  // and at least one social media field is valid
19  // lets proceed to submit the form now
20} else {
21    // lets tell the user we are serious here
22    // this will happen really fast since we are short-circuiting 
23    // with .some and .every above
24}

如上所述, Array.of() 中的所有函数都对特定表单字段进行验证,然后返回 truefalse. 由于 Array.of() 是从所给定参数构建数组的工厂(验证功能在我们的情况下),这意味着 optionalFields' 最终会看起来像 [true, false, false]'。 因此,如果 `requiredFields' 中的任何值都是假的,或者如果任何一个 'optionalFields' 都不是真的,我们不会提交表单。

结论

这个探索有助于揭示 MFR的突出地位,它还揭示了为什么.every().some()可以为您提供更有效的策略来操纵数组。

以下是促進或採用.some() 或.every()` 在您的 JavaScript 代碼庫中的幾個可能的情況:

  • A .filter() 随后立即由 .forEach(), .map(), .reduce(),或涉及它们的任何连锁组合
  • A .filter() 随后立即由检查召唤结果的条件,并且该条件的体内包含一个 .forEach(), .map(), .reduce(),或涉及它们的任何连锁组合,作用于 .filter() 的结果。

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

Published At
Categories with 技术
comments powered by Disqus