Array.forEach短路就像调用break


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

如何使用forEachJavaScript中的新方法执行此操作?我试过return;return false;breakbreak崩溃,return除了继续迭代外什么也不做。


6
值得注意的是,尽管return确实会继续执行迭代,但它将跳过该块之后的所有代码。以下面的代码为例:[1,2,3].forEach(function(el) { if(el === 2) { console.log(`Match on 2!`); return; } console.log(el); });console.log(el);匹配2时将跳过。
Shane

5
TL; DR:我会为您节省很多时间。我最近一直在使用很多JS。您可能正在寻找的答案(共28个...)是这样的:stackoverflow.com/a/32101207/1599699
Andrew

Answers:


2140

有没有内置的能力breakforEach。要中断执行,您将必须抛出某种异常。例如。

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 Fifth Edition方法,都需要将它们添加到Array.prototype缺少它们的浏览器中。


109
这不仅比使用普通的for循环更具可读性,而且性能更高。答案应该是“在这种情况下不要使用forEach” -1
英国电信

37
我认为“一些”在这里很好,为什么不使用提前退出优化
chrismarx

28
感谢您的关注someevery,这应该在答案中。无法理解为什么人们认为它的可读性较差。太棒了!
卡尔·阿德勒

9
的使用Array#some真的很好。首先,它与包括ie9和firefox 1.5在内的大多数浏览器兼容,也可以很好地工作。我的示例用例将是在range [a,b]数组中找到索引,其中一个数字在下边界和上边界对之间,测试并在找到时返回true。for..of尽管仅适用于较新的浏览器,它将是次之的最佳解决方案。
Sojimaxi

95
绝对不要将异常处理用作控制流。期。
坦率

479

现在,使用新的for循环在ECMAScript2015(又名ES6)中,还有一种更好的方法可以执行此操作。例如,此代码不会在数字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;
}

4
@superhero您可以在for ... of循环中获取元素的索引,只需使用即可entries。for(someArray.entries()的(const [index,element]]){// ...}
黑化

不建议不要将...用于数组吗?
schehata

4
@emostafa您将正确循环不被推荐用于阵列,但这方法实际上采用的是对循环。
canac

这是“针对”的,这是一个非常干净的解决方案……但这也是ES6的功能,因此请注意,仅当您的环境针对ES6设置时,此功能才有效。
乍得

我发现自己经常使用此解决方案,并且也将其用于对象。使用对象,您可以执行操作Object.entries(myObject),然后完全像for..in对数组使用一样使用它。请注意,JS数组基本上是幕后
安德鲁(Andrew)

204

您可以使用每种方法:

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

ES6

[1,2,3].every( el => 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;
  };
}

更多细节在这里


10
ES6现在[1,2,3].every( el => el !== 1 )
变得

1
@Valdemar,但是可以every 保证调用按顺序进行吗?
Pacerier

4
@Pacerier,您可以在ES6规范中看到索引k从0开始并以1递增的算法:http
XP1

@ XP1,是否所有实施者都必须这样做?
Pacerier '17年

1
@Pacerier,是的,大多数流行的实现都能正常工作。如果您担心嵌入式实现,通常是Opera或Webkit。方法每个方法对数组中存在的每个元素以升序调用一次callbackfn ,直到找到其中callbackfn返回false的元素为止。还要看步骤7。设k为0和8.e将k加1。–
Valdemar_Rudolfovich

78

引用以下MDN文档中的内容Array.prototype.forEach()

没有办法阻止或打破一个forEach()比抛出异常等循环。如果您需要这种行为,则该.forEach()方法是错误的工具,请改用普通循环。如果要为谓词测试数组元素并且需要布尔返回值,则可以使用every()some()代替。

对于@bobince建议的代码(在问题中),请Array.prototype.some()改用。它非常适合您的用例。

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


1
这是正确的答案。“一些”确实执行了foreach / break的操作。循环直到迭代n = true。
安东尼·布斯,

74

不幸的是在这种情况下,如果不使用它将更好forEach。而是使用常规for循环,它现在将完全按预期运行。

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

27
令我震惊的是,与正确答案的更高性能,更少的代码和更好的可读性相比,最高的投票可能是最糟糕的实现。抛出异常...真的吗?传统的for循环是否还不够靠谱?
gdbj

2
@gdbj我同意您的声明,并使用了这种方法,但真正令我震惊的是,没有这些技巧,没有办法退出forEach了,现在这是不好的设计。
ScottN

28

考虑使用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');
// => []

1
使用jQuery的充分理由。仍然缺少本机javascript中的forEach。
亚历

3
@AlexGrande jQuery的forEach和JavaScript的forEach不兼容。
比约恩

10
在很多地方都不能使用JavaScript,而jQuery并不是一个选择。
JBR威尔金森'16


18

如果您想使用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函数(如果已存在)。如果尚不存在,则不会对其进行修改。


11

简短答案:for...break为此或使用您的代码来避免破坏forEach。不要使用.some().every()模仿for...break。重写代码以避免for...break循环,或使用for...break。每次您使用这些方法作为for...break替代方法时,上帝都会杀死小猫。

长答案:

.some().every()两者都返回boolean值,如果传递的函数为其.some()返回true任何元素,则返回truefalse如果传递的函数为其返回元素,则每个返回值false。这就是功能的含义。使用函数的含义并不比使用表而不是CSS差得多,因为使用函数会使所有阅读您代码的人感到沮丧。

另外,使用这些方法作为for...break替代方法的唯一可能方法是产生副作用(在.some()回调函数外部更改某些var ),这与并无太大区别for...break

因此,使用.some()or .every()作为for...break循环替代品并非没有副作用,那么这还不算干净for...break,这并,这令人沮丧,因此还不是更好。

您始终可以重写代码,这样就不需要了for...break。您可以使用筛选数组.filter(),可以使用.slice()等拆分数组,然后对数组的该部分使用.forEach().map()


实际上,使用.filter是解决许多用例的合适解决方案。
TKoL

性能如何?如果频繁使用,过滤器会不会影响性能?
tfrascaroli

是的,过滤器阵列原型可能很重。我喜欢它,但是如果使用过度,它可能会影响性能。
乍得

@tfrascaroli for...break如果需要性能,请使用循环。for循环是最高效的迭代工具比.forEach().any().map().filter()
马克斯

6

这只是我想出的解决问题的方法...我很确定它可以解决原始申请者所遇到的问题:

Array.prototype.each = function(callback){
    if(!callback) return false;
    for(var i=0; i<this.length; i++){
        if(callback(this[i], i) == false) break;
    }
};

然后,您可以使用以下命令进行调用:

var myarray = [1,2,3];
myarray.each(function(item, index){
    // do something with the item
    // if(item != somecondition) return false; 
});

在回调函数内返回false将导致中断。让我知道那是否实际上不起作用。


1
=== false可能比== false这样做更好,因此您不必显式返回true(或真实值)即可继续循环,以免某些控制路径不返回值并且循环意外中断。
杰克

6

我想到的另一个概念是:

function forEach(array, cb) {
  var shouldBreak;
  function _break() { shouldBreak = true; }
  for (var i = 0, bound = array.length; i < bound; ++i) {
    if (shouldBreak) { break; }
    cb(array[i], i, array, _break);
  }
}

// Usage

forEach(['a','b','c','d','e','f'], function (char, i, array, _break) {
  console.log(i, char);
  if (i === 2) { _break(); }
});


语法类似于[NSArray enumerateObjectsUsingBlock],谢谢!
Chrstph SLN

@Drenai签名类似于本地人Array.prototype.forEach()forbreak早已存在这个问题,有人问前; OP会使用功能更强大的来寻找该行为forEach
c24w

@Drenai现在删除了他们的评论(但留下了反对票),其中提到该解决方案的签名很难记住,并且在您可以使用for...inand 解决问题时是不必要的break
c24w

5

在另一个站点上找到了此解决方案。您可以在try / catch场景中包装forEach。

if(typeof StopIteration == "undefined") {
 StopIteration = new Error("StopIteration");
}

try {
  [1,2,3].forEach(function(el){
    alert(el);
    if(el === 1) throw StopIteration;
  });
} catch(error) { if(error != StopIteration) throw error; }

此处有更多详细信息:http : //dean.edwards.name/weblog/2006/07/enum/


2
不要将异常用作控制流语句。用它来处理意外的结果。
最多


4

如果不需要在迭代后访问数组,则可以通过将数组的长度设置为0来纾困。如果在迭代后仍需要它,则可以使用slice克隆它。

[1,3,4,5,6,7,8,244,3,5,2].forEach(function (item, index, arr) {
  if (index === 3) arr.length = 0;
});

或使用克隆:

var x = [1,3,4,5,6,7,8,244,3,5,2];

x.slice().forEach(function (item, index, arr) {
  if (index === 3) arr.length = 0;
});

与在代码中引发随机错误相比,这是一个更好的解决方案。


做得很好:)但如果在分配array.length0它们后有一些操作将在当前迭代中应用,那么return在分配后可能最好使用
zhibirc

4

这是一个for循环,但是像forEach()一样在循环中维护对象引用,但是您可以将其拆分。

var arr = [1,2,3];
for (var i = 0, el; el = arr[i]; i++) {
    if(el === 1) break;
}

4

如前所述,您不能中断.forEach()

这是使用ES6迭代器进行foreach的一种更现代的方法。允许您在迭代时直接访问index/ value

const array = ['one', 'two', 'three'];

for (const [index, val] of array.entries()) {
  console.log('item:', { index, val });
  if (index === 1) {
    console.log('break!');
    break;
  }
}

输出:

item: { index: 0, val: 'one' }
item: { index: 1, val: 'two' }
break!

链接


3

另一种方法

        var wageType = types.filter(function(element){
            if(e.params.data.text == element.name){ 
                return element;
            }
        });
        console.dir(wageType);

2

我为此目的使用nullhack,它尝试访问的属性null,这是一个错误:

try {
  [1,2,3,4,5]
  .forEach(
    function ( val, idx, arr ) {
      if ( val == 3 ) null.NULLBREAK;
    }
  );
} catch (e) {
  // e <=> TypeError: null has no properties
}
//

1
为什么不只是throw BREAK呢?
Bergi 2015年

1

如果要保留forEach语法,这是一种保持效率的方法(尽管不如常规的for循环好)。立即检查一个变量,该变量知道您是否要退出循环。

本示例使用匿名函数在需要存储已完成信息的函数范围周围创建函数范围forEach

(function(){
    var element = document.getElementById('printed-result');
    var done = false;
    [1,2,3,4].forEach(function(item){
        if(done){ return; }
        var text = document.createTextNode(item);
        element.appendChild(text);
        if (item === 2){
          done = true;
          return;
        }
    });
})();
<div id="printed-result"></div>

我的两分钱。


1

我知道这是不正确的方法。这不是打破循环。这是一个神态

let result = true;
[1, 2, 3].forEach(function(el) {
    if(result){
      console.log(el);
      if (el === 2){
        result = false;
      }
    }
});



0

同意@bobince,赞成。

此外,仅供参考:

Prototype.js为此目的提供了一些东西:

<script type="text/javascript">
  $$('a').each(function(el, idx) {
    if ( /* break condition */ ) throw $break;
    // do something
  });
</script>

$break 将由Prototype.js在内部捕获和处理,从而打破了“每个”周期,但不会产生外部错误。

有关详细信息,请参见Prototype.JS API

jQuery还有一种方法,只需在处理程序中返回false即可尽早中断循环:

<script type="text/javascript">
  jQuery('a').each( function(idx) {
    if ( /* break condition */ ) return false;
    // do something

  });
</script>

有关详细信息,请参见jQuery API


0

这不是最有效的,因为您仍然循环所有元素,但是我认为可能值得考虑一下非常简单的方法:

let keepGoing = true;
things.forEach( (thing) => {
  if (noMore) keepGoing = false;
  if (keepGoing) {
     // do things with thing
  }
});

continue是关键字,您的代码是语法错误。
Bergi 2015年

3
既然您仍在使用ES6,则应该像往常一样切换到for of循环break;
Bergi 2015年

固定且正确-但为了简洁起见,大多使用es6
martyman 2015年

0

您可以按照以下适用于我的代码进行操作:

 var     loopStop = false;
YOUR_ARRAY.forEach(function loop(){
    if(loopStop){ return; }
    if(condition){ loopStop = true; }
});

为什么是-1?它比捕获异常并不难看,这是一个更大的hack恕我直言。
拜伦·惠特洛克

0

我更喜欢使用 for in

var words = ['a', 'b', 'c'];
var text = '';
for (x in words) {
    if (words[x] == 'b') continue;
    text += words[x];
}
console.log(text);

for in的工作方式与相似forEach,您可以在其中添加return to exit功能。性能也更好。


0

如果需要根据情况中断数组中元素的值(例如,中断条件不依赖于在分配数组元素值后可能会更改的运行时变量),也可以使用组合的切片()的indexOf()如下。

如果您需要在forEach到达“ Apple”时休息一下,可以使用

var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var fruitsToLoop = fruits.slice(0, fruits.indexOf("Apple"));
// fruitsToLoop = Banana,Orange,Lemon

fruitsToLoop.forEach(function(el) {
    // no need to break
});

W3Schools.com中所述,slice()方法将数组中选定的元素作为新的数组对象返回。原始阵列将不会更改。

JSFiddle查看

希望它可以帮助某人。


0

您可以创建一个变种forEach,它允许breakcontinuereturn甚至async/ await:(例如写在打字稿)

export type LoopControlOp = "break" | "continue" | ["return", any];
export type LoopFunc<T> = (value: T, index: number, array: T[])=>LoopControlOp;

Array.prototype.ForEach = function ForEach<T>(this: T[], func: LoopFunc<T>) {
    for (let i = 0; i < this.length; i++) {
        const controlOp = func(this[i], i, this);
        if (controlOp == "break") break;
        if (controlOp == "continue") continue;
        if (controlOp instanceof Array) return controlOp[1];
    }
};

// this variant lets you use async/await in the loop-func, with the loop "awaiting" for each entry
Array.prototype.ForEachAsync = async function ForEachAsync<T>(this: T[], func: LoopFunc<T>) {
    for (let i = 0; i < this.length; i++) {
        const controlOp = await func(this[i], i, this);
        if (controlOp == "break") break;
        if (controlOp == "continue") continue;
        if (controlOp instanceof Array) return controlOp[1];
    }
};

用法:

function GetCoffee() {
    const cancelReason = peopleOnStreet.ForEach((person, index)=> {
        if (index == 0) return "continue";
        if (person.type == "friend") return "break";
        if (person.type == "boss") return ["return", "nevermind"];
    });
    if (cancelReason) console.log("Coffee canceled because: " + cancelReason);
}

-1

尝试“查找”:

var myCategories = [
 {category: "start", name: "Start", color: "#AC193D"},
 {category: "action", name: "Action", color: "#8C0095"},
 {category: "exit", name: "Exit", color: "#008A00"}
];

function findCategory(category) {
  return myCategories.find(function(element) {
    return element.category === category;
  });
}

console.log(findCategory("start"));
// output: { category: "start", name: "Start", color: "#AC193D" }

-1

是的,可以继续并退出forEach循环。

要继续,可以使用return,循环将继续,但当前函数将结束。

要退出循环,可以将第三个参数设置为0 length,设置为空数组。循环不会继续,当前函数会继续,因此您可以使用“返回”完成操作,就像在正常的for循环中退出...

这个:

[1,2,3,4,5,6,7,8,9,10].forEach((a,b,c) => {
    console.log(a);
    if(b == 2){return;}
    if(b == 4){c.length = 0;return;}
    console.log("next...",b);
});

将打印此:

1
next... 0
2
next... 1
3
4
next... 3
5

-2

以前,我的代码在下面

 this.state.itemsDataSource.forEach((item: any) => {
                if (!item.isByPass && (item.invoiceDate == null || item.invoiceNumber == 0)) {

                    return false;
                }
            });

我已更改为以下内容,它已修复。

 for (var i = 0; i < this.state.itemsDataSource.length; i++) {
                var item = this.state.itemsDataSource[i];
                if (!item.isByPass && (item.invoiceDate == null || item.invoiceNumber == 0)) {

                    return false;
                }
            }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.