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


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

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


6
Варто зауважити, що, хоча returnдійсно продовжується ітерація, вона пропустить будь-який код, що з’являється після нього в блоці. Візьміть цей код , наприклад: [1,2,3].forEach(function(el) { if(el === 2) { console.log(`Match on 2!`); return; } console.log(el); });.The console.log(el);буде пропущено , коли 2 підібраний.
Шейн

5
TL; DR: Я збережу більшість з вас багато часу. Я використовую багато JS останнім часом. Відповідь (з 28 ...), яку ви, мабуть, шукаєте, така: stackoverflow.com/a/32101207/1599699
Andrew

Відповіді:


2141

Немає вбудованої можливості для 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(який зупиниться на a return false), і forEachвсі методи ECMAScript Fifth Edition, які потрібно буде додати до Array.prototypeбраузерів, де вони відсутні.


110
Це ні читабельніше, ні ефективніше, ніж просто використання нормального для циклу. Відповідь повинна бути "не використовувати для цього в кожному випадку" -1
BT

37
Я думаю, що "дещо" тут добре, чому б не використати оптимізацію раннього виходу
chrismarx

28
Дякуємо за увагу, someі everyце має бути ТОП у відповіді. Не можу зрозуміти, чому люди думають, що це менш читабельно. Це просто приголомшливо!
Карл Адлер

9
Використання Array#someсправді приємно. По-перше, його сумісність з більшістю браузерів, включаючи ie9 та firefox 1.5, також працює дуже добре. Моїм прикладом використання буде пошук індексу в масиві діапазонів [a, b], де число знаходиться між нижньою граничною і верхньою граничною парою, перевірити і повернути істинне, коли знайдене. for..ofбуло б наступним найкращим рішенням, хоча тільки для нових браузерів.
Sojimaxi

95
Поводження з винятками НІКОЛИ не слід використовувати як контрольний потік. ПЕРІОД.
відвертий

479

Зараз є ще кращий спосіб зробити це в 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 ... повторюється кількість перелічених властивостей об'єкта в оригінальному порядку вставки. Оператор for ... повторює дані, які ітерабельний об'єкт визначає для повторення.

Потрібен індекс в ітерації? Ви можете використовувати Array.entries():

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

4
@superhero Ви можете отримати індекс елемента в циклі for для ..., просто потрібно використовувати entries. for (const [індекс, елемент] someArray.entries ()) {// ...}
чорношкірий

чи не рекомендується не використовувати для ... в масивах?
schehata

4
@emostafa Ви правильно про для в циклах не рекомендується для масивів, але це на самому справі підхід використовує для з циклу.
canac

Це "за", і це дійсно чисте рішення ... але це також функція ES6, тому просто пам’ятайте, що це буде працювати лише в тому випадку, якщо ваше середовище налаштоване на ES6.
Чад

Мені здається, що я дуже часто використовую це рішення, і я його також використовую для об'єктів. З об'єктами ви можете це робити, Object.entries(myObject)а потім використовувати саме так, як і for..inдля масиву. Зауважте, що масиви JS - це в основному об'єкти під кришкою: blog.niftysnippets.org/2011/01/myth-of-arrays.html
Ендрю

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://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every
XP1

@ XP1, Чи всі виконавці зобов'язані зробити це саме так?
Pacerier

1
@Pacerier, так, більшість популярних програм працює належним чином. Якщо ви стурбовані вбудованими реалізаціями, зазвичай це Opera чи веб-сайт. Метод кожного виклику callbackfn один раз для кожного елемента, присутнього в масиві, у порядку зростання , поки він не знайде той, де callbackfn повертається помилковим. Також подивіться на крок 7. Нехай k дорівнює 0. і 8.e Збільште k на 1.
Вальдемар_Рудольфович

78

Цитуючи документацію MDN проArray.prototype.forEach() :

Там немає ніякого способу , щоб зупинити або зламати в forEach()петлю іншого , ніж кидати виняток. Якщо вам потрібна така поведінка, .forEach()метод є неправильним інструментом , замість цього скористайтеся простою петлею. Якщо ви тестуєте елементи масиву на присудок і вам потрібно булеве повернене значення, ви можете використовувати every()або some()замість цього.

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

Array.prototype.some()виконує функцію зворотного виклику один раз для кожного елемента, присутнього в масиві, доки він не знайде той, де зворотний виклик повертає truthy значення (значення, яке стає істинним при перетворенні в a Boolean). Якщо такий елемент знайдений, some()негайно повертає true. В іншому випадку some()повертається помилковим. зворотний виклик викликається лише для індексів масиву, яким призначені значення; він не викликається для індексів, які були видалені або яким ніколи не було призначено значень.


1
Це правильна відповідь. 'хтось' робить саме те, що робив би передрік / перерва. Він циклічний, поки ітерація n = true.
Антоні Бут

74

На жаль, у цьому випадку буде набагато краще, якщо ви не користуєтесь цим forEach. Замість цього використовуйте звичайний forцикл, і він буде працювати точно так, як ви очікували.

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

27
Мене шокує, що найвищий голос - це найгірша можлива реалізація, порівняно з більш ефективними, меншими кодами та кращою читабельністю цієї правильної відповіді. Викиньте виняток ... справді? Хіба традиційного для циклу просто не достатньо kewl?
gdbj

2
@gdbj Я погоджуюся з вашим твердженням і використовував цей метод, але те, що мене насправді шокує, немає можливості вийти з forEach без цих хак, тепер це поганий дизайн.
Скотн

28

Розглянемо використання jquery«s eachметод, так як він дозволяє повернути помилкову функцію зворотного виклику всередині:

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

Бібліотеки Лодаша також надають takeWhileметод, який можна зв'язати з картою / зменшити / скласти тощо:

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 не сумісні.
Бьорн

10
JavaScript використовується в багатьох місцях, що jQuery не є варіантом.
JBRWilkinson


18

Якщо ви хочете скористатись пропозицією Діна Едуарда та викинути помилку 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;
        }
      }
    };
  }
})();

Вищевказаний код дасть вам змогу запускати такий код, як наведений нижче, без необхідності виконувати власні пробні пропозиції:

// 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якщо повертається який-небудь елемент, для якого передана функція повертається true, кожен повертається, falseякщо повертається який-небудь елемент, для якого передана функція false. Це те, що означають ці функції. Використання функцій для того, що вони не означають, набагато гірше, ніж використання таблиць для компонування замість CSS, оскільки це засмучує всіх, хто читає ваш код.

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

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

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


використання .filter - це фактично відповідне рішення для багатьох випадків використання для злому.
TKoL

А як щодо продуктивності? Чи не фільтруватиме вплив на продуктивність при частому використанні?
tfrascaroli

Так, прототип масиву фільтрів може стати важким. Мені це подобається, але це може призвести до продуктивності при надмірному використанні.
Чад

@tfrascaroli використовуйте for...breakцикл, якщо вам потрібна продуктивність. forпетля є найбільш продуктивним інструментом ітерації , ніж .forEach(), .any(), .map(), і .filter()т.д.
Max

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

Повернення помилки всередині функції зворотного виклику призведе до перерви. Дайте мені знати, якщо це насправді не працює.


1
=== falseможе бути кращим, ніж == falseтому вам не доведеться явно повертати true (або truthy значення), щоб продовжувати цикл, щоб уникнути, що якийсь контрольний шлях не поверне значення, і цикл несподівано зламається.
Джейк

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(). forі breakіснувало задовго до того, як було задано це питання; ОП шукали таку поведінку , використовуючи, тим більш функціональний, forEach.
c24w

@Drenai тепер видалив їх коментар (але залишив нижчу заявку), де згадувалося, що підпис цього рішення важко запам’ятати і непотрібний, коли можна вирішити проблему з for...inі break.
c24w

5

Знайшли це рішення на іншому сайті. Ви можете обернути 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. Якщо вам все-таки потрібен після ітерації, ви можете клонувати його за допомогою фрагмента ..

[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.lengthна 0вони будуть застосовуватися в поточній ітерації, так що, ймовірно , це іноді краще використовувати 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. Дозволяє отримати прямий доступ до 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?
Бергі

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>

Мої два центи.




0

Погодьтеся з @bobince, схвалено.

Також FYI:

Для цього в Prototype.js є щось:

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

$break буде захоплено та оброблено прототипом.js внутрішньо, порушуючи цикл "кожен", але не генеруючи зовнішні помилки.

Докладніше див. API Prototype.JS .

jQuery також має спосіб, просто поверніть false в оброблювач, щоб рано зламати цикл:

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

  });
</script>

Докладніше див. API jQuery .


0

Це не найефективніше, оскільки ти все ще циркулюєш усі елементи, але я подумав, що варто обдумати дуже просте:

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

continueце ключове слово, ваш код - синтаксична помилка.
Бергі

3
Зважаючи на те, що ви в будь-якому випадку використовуєте ES6, вам слід просто перейти на for ofцикл і break;з цього, як завжди.
Бергі

виправлено і правдиво - але в основному використовували es6 для стислості
мартиман


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

Якщо вам потрібно розбити виходячи зі значення елементів, які вже є у вашому масиві, як у вашому випадку (тобто якщо умова розриву не залежить від змінної часу виконання, яка може змінитися після призначення масиву значень його елементів), ви також можете використовувати комбінацію з зрізу () і 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що дозволяє break, continue, returnі навіть 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 довжину, встановити порожній масив. Цикл не буде продовжуватися, поточна функція виконує, тому ви можете використовувати "return" для завершення, як вихід у нормальному для циклу ...

Це:

[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;
                }
            }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.