题 如何短路Array.forEach就像调用break?


[1,2,3].forEach(function(el) {
    if(el === 1) break;
});

我怎么能用新的呢? forEach JavaScript中的方法?我试过“返回”,“返回假”和“休息”。打破崩溃并返回只会继续迭代。


1011
2018-04-14 21:57


起源




答案:


没有内置的能力 break 在 forEach。要中断执行,你必须抛出某种异常。例如。

var BreakException = {};

try {
  [1, 2, 3].forEach(function(el) {
    console.log(el);
    if (el === 2) throw BreakException;
  });
} catch (e) {
  if (e !== BreakException) throw e;
}

JavaScript异常并不是非常漂亮。传统的 for 如果你真的需要,循环可能更合适 break 在里面。

使用 Array#some

相反,使用 Array#some

[1, 2, 3].some(function(el) {
  console.log(el);
  return el === 2;
});

这是因为 some 回报 true 只要以数组顺序执行的任何回调返回 true,短路其余的执行。

some,它的逆 every (这将停止在 return false),和 forEach 所有ECMAScript第五版方法都需要添加到 Array.prototype 在他们失踪的浏览器上。


1411
2018-04-14 22:02



谢谢。我不知道一些。例外情况似乎太大了。在这种情况下,我宁愿只使用它。 - Scott
@Scott,你不能使用 for...in 对于数组 - user422039
@ pepkin88 for...in 即使 hasOwnProperty 不保证工作相同 forEach。特别是您可能(有时会这样做,取决于浏览器)以“错误”的顺序获取属性。即使在你不在乎的情况下,我也会避免它。然后,这个论点与这个问题无关;我认为user422039只是误读了斯科特所说的使用方法 for。 - bobince
我认为他的意思是:“我宁愿使用 for 在那种情况下。“而且 for 显然是对的。 - Rudie
请不要使用 Array#some 要么 Array#every 为了这种任务。使用 Array#filter 过滤源数组,然后使用 Array#forEach 要么 Array#map 处理结果集,直到你需要性能优化(在这种情况下使用普通的旧 for...break 循环)。这是 许多 更具可读性。 - Max


现在有一种更好的方法可以在ECMAScript2015(又名ES6)中使用新的方法 for循环。例如,此代码不会在数字5后打印数组元素:

let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let el of arr) {
  console.log(el);
  if (el === 5) {
    break;
  }
}

来自文档:

对于在... 和 为...的 语句迭代某事。它们之间的主要区别在于它们的迭代。该 对于在... 语句以原始插入顺序迭代对象的可枚举属性。该 为...的 语句迭代可迭代对象定义为迭代的数据。

在迭代中需要索引吗?您可以使用 Array.entries()

for (const [index, el] of arr.entries()) {
  if ( index === 5 ) break;
}

166
2017-08-19 16:43



......不,不应该折旧。有时你需要循环的对象中的参数索引,这是你无法进入的 for of 要么 for in 循环... - superhero
这很好,因为一些可迭代的对象就像 Map 不支持其他一些解决方案 every, some 等,但这很好。 - anthonygore
@superhero你可以在for循环中得到元素的索引,你只需要使用 entries。 for(const [index,element] of someArray.entries()){// ...} - blackxored
@blackxored是的,但你错过了我想说的一点。顺便说一下,我相信我在这里的评论是回应某人现已删除的评论,不确定...... - superhero
@emostafa你是对的 在 循环不推荐用于数组,但这种方法实际上是使用for 的 循环。 - canac


您可以使用 一切 方法:

[1,2,3].every(function(el) {
    return !(el === 1);
});

对于旧浏览器支持使用:

if (!Array.prototype.every)
{
  Array.prototype.every = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          !fun.call(thisp, this[i], i, this))
        return false;
    }

    return true;
  };
}

更多细节 这里


154
2017-07-19 09:18



+1为旧浏览器的垫片。它 真 不应该需要(谁仍然编码IE8?)但是它很有用。 - tfrascaroli
这比抛出异常要好,应该排名更高的IMO。 - nycynik
现在ES6漂亮干净了 - [1,2,3].every( el => el !== 1 ) - metame
@Valdemar,但是 every  保证 这些电话按顺序进行? - Pacerier
@Pacerier,你可以在ES6规范中看到该算法的索引 k从0开始并递增1: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every - XP1


引自 MDN文档 Array.prototype.forEach()

无法停止或休息 一个 forEach() 循环以外的   通过抛出异常。如果你需要这样的行为,那么 .forEach() 方法是 错误的工具,改为使用普通循环。如果要测试谓词的数组元素并需要布尔返回值,则可以使用 every() 要么 some() 代替。

对于你的代码(在问题中),正如@bobince建议的那样,使用 Array.prototype.some() 代替。它非常适合您的用例。

Array.prototype.some() 对数组中存在的每个元素执行一次回调函数,直到找到一个回调返回truthy值的值(转换为a时变为true的值) Boolean)。如果找到这样的元素, some() 立即返回true。除此以外, some() 返回false。仅对已分配值的数组的索引调用回调;对于已删除的索引或从未分配过值的索引,不会调用它。


45
2018-01-06 21:16





不幸的是,在这种情况下,如果你不使用它会好得多 forEach。 而是使用常规 for 循环,它现在将完全按照您的预期工作。

var array = [1, 2, 3];
for (var i = 0; i < array.length; i++) {
  if (array[i] === 1){
    break;
  }
}

39
2017-08-25 20:03



令我震惊的是,与表现更好,代码更少,以及更正确的答案的可读性相比,最高票数是可能的最差实施。抛出异常...真的吗?传统的for循环是不是足够了? - gdbj
@gdbj我同意你的陈述并使用了这种方法,但真正令我震惊的是没有这些黑客就没有办法退出forEach,现在这是糟糕的设计。 - ScottN


考虑使用 jqueryeach 方法,因为它允许在回调函数内返回false:

$.each(function(e, i) { 
   if (i % 2) return false;
   console.log(e)
})

Lodash图书馆也提供 takeWhile 可以用map / reduce / fold等链接的方法:

var users = [
  { 'user': 'barney',  'active': false },
  { 'user': 'fred',    'active': false },
  { 'user': 'pebbles', 'active': true }
];

_.takeWhile(users, function(o) { return !o.active; });
// => objects for ['barney', 'fred']

// The `_.matches` iteratee shorthand.
_.takeWhile(users, { 'user': 'barney', 'active': false });
// => objects for ['barney']

// The `_.matchesProperty` iteratee shorthand.
_.takeWhile(users, ['active', false]);
// => objects for ['barney', 'fred']

// The `_.property` iteratee shorthand.
_.takeWhile(users, 'active');
// => []

24
2018-04-14 22:06



使用jQuery的好理由。原生javascript中的forEach仍然缺乏。 - Alex Grande
@AlexGrande jQuery的forEach和JavaScript的forEach不兼容。 - Bjorn Tipling
JavaScript在许多地方使用,jQuery不是一个选项。 - JBRWilkinson


如果你想使用 Dean Edward的建议 并抛出StopIteration错误以突破循环而不必捕获错误,您可以使用以下函数(最初来自这里):

// Use a closure to prevent the global namespace from be polluted.
(function() {
  // Define StopIteration as part of the global scope if it
  // isn't already defined.
  if(typeof StopIteration == "undefined") {
    StopIteration = new Error("StopIteration");
  }

  // The original version of Array.prototype.forEach.
  var oldForEach = Array.prototype.forEach;

  // If forEach actually exists, define forEach so you can
  // break out of it by throwing StopIteration.  Allow
  // other errors will be thrown as normal.
  if(oldForEach) {
    Array.prototype.forEach = function() {
      try {
        oldForEach.apply(this, [].slice.call(arguments, 0));
      }
      catch(e) {
        if(e !== StopIteration) {
          throw e;
        }
      }
    };
  }
})();

上面的代码将使您能够运行以下代码而无需执行自己的try-catch子句:

// Show the contents until you get to "2".
[0,1,2,3,4].forEach(function(val) {
  if(val == 2)
    throw StopIteration;
  alert(val);
});

需要记住的一件重要事情是,只有已存在的Array.prototype.forEach函数才会更新。如果它不存在,则不会修改它。


14
2017-07-05 19:32





从你的代码示例看,它看起来像 Array.prototype.find 是你在找什么: Array.prototype.find() 和 Array.prototype.findIndex() 

[1, 2, 3].find(function(el) {
    return el === 2;
}); // returns 2

13
2018-01-15 20:20