Короткое замыкание Array.forEach как вызов прерывания

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

Как это сделать, используя новый метод forEach в JavaScript? Я попробовал return; , return false; и break. break и return ничего, кроме продолжения итерации.

Теги:
arrays

28 ответов

1514
Лучший ответ

Нет встроенной возможности 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 Fifth Edition, которые нужно будет добавить в Array.prototype в браузерах, где они отсутствуют.

  • 27
    Это не более читабельно и не более производительно, чем просто использование нормального цикла for. Ответ должен быть «не используйте forEach в этом случае» -1
  • 0
    Как уже говорилось выше, это технически интересный хак, но, поскольку он подрывает и вводит в заблуждение предполагаемый смысл some / every , это очень большой взлом. Считается вредным. Использование старой школы for и / или предварительной фильтрации / нарезки массива не так привлекательно, но это лучшие идеи.
Показать ещё 10 комментариев
210

Теперь есть еще лучший способ сделать это в 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;
  }
}

Из документов:

И для... внутри, так и для... заявлений перебирают что-то. Основное различие между ними заключается в том, что они перебирают. Оператор for... in выполняет итерацию по перечислимым свойствам объекта в исходном порядке вставки. Оператор for... выполняет итерацию по данным, которые итерируемый объект определяет для повторения.

Нужен ли индекс на итерации? Вы можете использовать Array.entries():

for (const [index, el] of arr.entries()) {
  if ( index === 5 ) break;
}
  • 3
    @superhero Вы можете получить индекс элемента в цикле for ..., вам просто нужно использовать entries . for (const [index, element] of someArray.entries ()) {// ...}
  • 0
    не рекомендуется не использовать для ... в массивах?
Показать ещё 1 комментарий
155

Вы можете использовать любой метод:

[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;
  };
}

подробнее здесь.

  • 8
    Хороший и чистый в ES6 сейчас - [1,2,3].every( el => el !== 1 )
  • 1
    @Valdemar, а every гарантируют, что звонки выполняются последовательно?
Показать ещё 3 комментария
52

Цитирование из документации MDN Array.prototype.forEach():

Нет никакого способа остановить или прервать цикл forEach() кроме как с помощью исключения. Если вам нужно такое поведение, метод .forEach() является неправильным инструментом, вместо этого используйте простой цикл. Если вы тестируете элементы массива на предмет предиката и вам нужно логическое возвращаемое значение, вы можете использовать вместо every() или some().

Для вашего кода (в вопросе), как предлагает @bobince, используйте вместо него Array.prototype.some(). Это очень хорошо подходит для вашего использования.

Array.prototype.some() выполняет функцию обратного вызова один раз для каждого элемента, присутствующего в массиве, пока не найдет элемент, в котором обратный вызов возвращает истинное значение (значение, которое становится истинным при преобразовании в Boolean). Если такой элемент найден, some() немедленно возвращает true. В противном случае some() возвращает false. обратный вызов вызывается только для индексов массива, которым присвоены значения; он не вызывается для индексов, которые были удалены или которым никогда не присваивались значения.

  • 1
    Это правильный ответ. 'some' делает именно то, что делает foreach / break. Это повторяется до итерации n = true.
46

К сожалению, в этом случае будет намного лучше, если вы не используете forEach. Вместо того, чтобы использовать обычную for цикла и теперь он будет работать точно так, как и следовало ожидать.

var array = [1, 2, 3];
for (var i = 0; i < array.length; i++) {
  if (array[i] === 1){
    break;
  }
}
  • 19
    Меня шокирует тот факт, что наибольшее число голосов является худшей из возможных реализаций по сравнению с более высокой производительностью, меньшим количеством кода и лучшей читаемостью этого правильного ответа. Брось исключение ... правда? Является ли традиционный цикл for просто недостаточно kewl?
  • 1
    @gdbj Я согласен с вашим утверждением и использовал этот метод, но что действительно шокирует меня, так это то, что без этих хаков невозможно выйти из forEach, теперь это плохой дизайн.
22

Рассмотрим использование jquery each метода, поскольку он позволяет возвращать false внутри функции обратного вызова:

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

В библиотеках Lodash также есть метод takeWhile который можно связать с map/ takeWhile/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. forEach в родном javascript по-прежнему отсутствует.
  • 3
    @AlexGrande jQuery forEach и JavaScript forEach не совместимы.
Показать ещё 1 комментарий
14

Если вы хотите использовать предложение Дина Эдварда и выбросить ошибку 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, если она уже существует. Если он еще не существует, он не будет изменять его.

13

Из примера вашего кода это выглядит так: Array.prototype.find - это то, что вы ищете: Array.prototype.find() и Array.prototype.findIndex()

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

Короткий ответ: используйте for...break для этого или измените свой код, чтобы избежать нарушения forEach. Не используйте .some() или .every() для эмуляции for...break. Перепишите код, чтобы избежать цикла for...break или используйте for...break. Каждый раз, когда вы используете эти методы как for...break, альтернативный Бог убивает котенка.

Длинный ответ:

.some() и .every() оба возвращают значение boolean, .some() возвращает true, если какой-либо элемент, для которого переданная функция возвращает true, каждый возвращает false, если есть какой-либо элемент, для которого передается функция возвращает false. Это то, что означают эти функции. Использование функций для того, что они не означают, намного хуже, чем использование таблиц для макета вместо CSS, потому что это расстраивает всех, кто читает ваш код.

Кроме того, единственным возможным способом использования этих методов в качестве альтернативы for...break является создание побочных эффектов (изменение некоторых vars вне функции обратного вызова .some()), и это не сильно отличается от for...break.

Таким образом, использование .some() или .every() как for...break альтернативы цикла не содержит побочных эффектов, это не намного чище, чем for...break, это расстраивает, так что это не лучше.

Вы всегда можете переписать свой код, чтобы в for...break не было необходимости. Вы можете фильтровать массив с помощью .filter(), вы можете разделить массив с помощью .slice() и т.д., А затем использовать .forEach() или .map() для этой части массива.

  • 0
    использование .filter на самом деле является подходящим решением для многих случаев взлома.
  • 0
    Как насчет производительности? Не повлияет ли фильтр на производительность при частом использовании?
4

Нашел это решение на другом сайте. Вы можете обернуть forEach в сценарий try/catch.

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/

3

Это цикл for, но поддерживает ссылку на объект в цикле, как и forEach(), но вы можете вырваться.

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

Если вам не нужно получать доступ к вашему массиву после итерации, вы можете выручить, установив длину массива в 0. Если вам все еще нужно это после вашей итерации, вы можете клонировать ее с помощью среза.

[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;
});

Это гораздо лучшее решение, которое бросает случайные ошибки в вашем коде.

  • 0
    молодец :) но если после присвоения array.length для 0 есть какие-то действия, они будут применяться в текущей итерации, поэтому, вероятно, иногда лучше использовать return после такого присвоения
2

Это только то, что я придумал, чтобы решить проблему... Я уверен, что она исправляет проблему, с которой столкнулся первоначальный консультант:

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 (или истинное значение), чтобы продолжить цикл, чтобы какой-нибудь путь управления не возвращал значение и цикл неожиданно оборвался.
1

Как упоминалось ранее, вы не можете сломать .forEach().

Здесь немного более современный способ сделать foreach с ES6 Iterators. Позволяет получить прямой доступ к 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!

связи

1

Я знаю, что это не так. Это не разрыв цикла. Это Джугад

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

Если вы хотите сохранить свой синтаксис forEach, это способ сохранить его эффективность (хотя и не так хороша, как регулярная для цикла). Немедленно проверьте переменную, которая знает, хотите ли вы выйти из цикла.

В этом примере используется анонимная функция для создания области функций вокруг 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

Я использую 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 ?
1

Еще одна концепция, которую я придумал:

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(); }
});
  • 0
    Есть ли причина для понижения?
0

Еще один подход

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

Правильный ответ зависит от того, почему вы хотите это сделать.

Если вы хотите: "как чистый и лаконичный способ выхода из forEach() насколько это возможно, учитывая, что break не разрешен", вы можете переопределить Array.forEach(), чтобы вы это сделали:

[1,2,3,4,5].forEach((x,i,stop) => { // x and i are the standard 2 args
  if (x > 3) {
    stop() // You could call it 'end', 'brk' or whatever...
  }
  console.log(x)
})

Вот переопределение, обратите внимание, что forEach() обычно передает два параметра: текущий итерационный объект и индекс. Мы просто добавляем третью:

Array.prototype.forEach = function(fn) {
  var StopIteration = new Error("StopIteration");
  var len = this.length;
  function stop() {
    throw StopIteration;
  }
  for (i=0;i<len;i++) {
    try {
      fn(this[i], i, stop)
    } 
    catch(e) {
      if(e == StopIteration) {
        return
      }
      throw e;
    }
  }
}

Вы можете использовать любое имя, которое вам нравится (кроме break или любого другого зарезервированного ключевого слова, вам не повезло!)

(В реальном проекте вы должны пойти с одним из превосходных ответов. Я просто добавляю это, потому что некоторые люди могут найти концепцию полезной в других приложениях).

  • 1
    Это случай загрязнения прототипа, тем более что Array.prototype.forEach обычно вызывает три аргумента: текущий элемент, индекс этого элемента и ссылку на весь Array .
0

попробуйте с помощью "find":

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" }
  • 0
    Это решение уже дано.
0

Если вам нужно разбиться на основе значений элементов, которые уже находятся в вашем массиве, как в вашем случае (т.е. Если условие break не зависит от переменной времени выполнения, которая может измениться после того, как массиву присвоены его значения элемента), вы также можете использовать комбинацию slice() и 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

Я предпочитаю использовать 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, и вы можете добавить возврат к функции выхода внутри. Лучшая производительность тоже.

0

вы можете следовать приведенному ниже коду:

 var     loopStop = false;
YOUR_ARRAY.forEach(function loop(){
    if(loopStop){ return; }
    if(condition){ loopStop = true; }
});
  • 0
    Почему -1? это не страшнее, чем поймать исключение, это больше взломать ИМХО.
0

Это не самый эффективный, так как вы все еще циклируете все элементы, но я подумал, что стоит подумать об очень простом:

let keepGoing = true;
things.forEach( (thing) => {
  if (noMore) keepGoing = false;
  if (keepGoing) {
     // do things with thing
  }
});
  • 0
    continue - это ключевое слово, ваш код - синтаксическая ошибка.
  • 2
    Учитывая, что вы все равно используете ES6, вам следует просто переключиться for of цикл for и break; как обычно.
Показать ещё 1 комментарий
0

Согласитесь с @bobince, сохранено.

Кроме того, FYI:

Prototype.js имеет что-то для этой цели:

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

$break будет проверяться и обрабатываться Prototype.js внутренне, прерывая "каждый" цикл, но не генерируя внешние ошибки.

Подробнее см.

Подробнее см. .

0

Используйте функцию array.prototype.every, которая предоставляет вам утилиту для прерывания цикла. Пример здесь Документация Javascript в сети разработчиков Mozilla

-1
var Book = {"Titles":[                          
    {
    "Book3" : "BULLETIN 3"
    }   
    ,
    {
    "Book1" : "BULLETIN 1"
    }
    ,
    {
    "Book2" : "BULLETIN 2"
    }    
]}

var findbystr = function(str) { 
    var return_val;
    Book.Titles.forEach(function(data){ 
        if(typeof data[str] != 'undefined')
        {
            return_val = data[str];
        } 
    }, str) 

    return return_val;
}

book = findbystr('Book1');
console.log(book);

Ещё вопросы

Сообщество Overcoder
Наверх
Меню