Какой лучший способ избавиться от вложенных циклов в JavaScript?


449

Какой лучший способ избавиться от вложенных циклов в Javascript?

//Write the links to the page.
for (var x = 0; x < Args.length; x++)
{
   for (var Heading in Navigation.Headings)
   {
      for (var Item in Navigation.Headings[Heading])
      {
         if (Args[x] == Navigation.Headings[Heading][Item].Name)
         {
            document.write("<a href=\"" 
               + Navigation.Headings[Heading][Item].URL + "\">" 
               + Navigation.Headings[Heading][Item].Name + "</a> : ");
            break; // <---HERE, I need to break out of two loops.
         }
      }
   }
}

Вот хороший пример выхода из циклов и блоков кода: marcin-chwedczuk.github.io/…
csharpfolk

Ответы:


1034

Так же, как Perl,

loop1:
    for (var i in set1) {
loop2:
        for (var j in set2) {
loop3:
            for (var k in set3) {
                break loop2;  // breaks out of loop3 and loop2
            }
        }
    }

как определено в EMCA-262 раздел 12.12. [MDN Docs]

В отличие от C, эти метки могут использоваться только для continue и break, а Javascript не имеет goto.


388
WTF, почему я не видел, чтобы это использовалось где-то в моих 3-х годах с JavaScript: / ..
Салман фон Аббас

40
MDN говорит «избегайте использования ярлыков» исключительно по соображениям удобства чтения. Почему это не «читабельно»? Потому что никто не использует их, конечно. Но почему они не используют их? ...
XML

7
@Web_Designer Я считаю, что ваш комментарий устарел. Нигде в документах MDN не говорится "избегать использования меток". Пожалуйста, рассмотрите возможность изменения или удаления вашего комментария.
Шон Боб

8
@ SeantheBean Готово. Это кажется более простым ответом и не допускает злоупотреблений, потому что он доступен только для continueи break.
Гэри Уиллоуби

29
@ JérémyPouyet - Ваша логика для голосования «вниз» бессмысленна и необоснованна. Это отвечает на вопрос ОП отлично. Вопрос не касается вашего мнения относительно разборчивости. Пожалуйста, пересмотрите свой подход к оказанию помощи сообществу.
Дембински

168

Заверните это в функцию, а затем просто return.


12
Я решил принять этот ответ, потому что он прост и может быть реализован элегантным способом. Я абсолютно ненавижу GOTO и считаю их плохой практикой ( могу открыть ), Ephemient's слишком близка к таковой. ; о)
Гэри Уиллоуби

16
ИМО, GOTO в порядке, пока они не нарушают структурирование. Но каждому свое!
ephemient

31
Метки циклов for не имеют абсолютно ничего общего с GOTO, за исключением их синтаксиса. Их просто нужно оторвать от внешних петель. У вас нет проблем с разрывом внутренней петли? так почему у вас проблемы с разрывом внешних петель?
Джон Смит

11
Пожалуйста, рассмотрите возможность принятия другого ответа. Если бы не комментарий Эндрю Хеджеса (спасибо, между прочим), я бы подумал: ах, поэтому в javascript такой функции нет. Бьюсь об заклад, многие в сообществе могут не заметить комментарий и думать точно так же.
Джон Смит

11
Почему в Stack Overflow нет функции, позволяющей сообществу отвергать явно неверно выбранный ответ? : /
Мэтт Хаггинс

85

Я немного опаздываю на вечеринку, но следующий подход не зависит от языка и не использует GOTO / ярлыки или перенос функций:

for (var x = Set1.length; x > 0; x--)
{
   for (var y = Set2.length; y > 0; y--)
   {
      for (var z = Set3.length; z > 0; z--)
      {
          z = y = -1; // terminates second loop
          // z = y = x = -1; // terminate first loop
      }
   }
}

С другой стороны, он течет естественно, что должно радовать толпу не GOTO. С другой стороны, внутренний цикл должен завершить текущую итерацию перед завершением, поэтому он может быть неприменим в некоторых сценариях.


2
открывающая скобка не должна быть в новых строках, потому что реализации js могут вставлять двоеточие в конце предыдущей строки.
Евгений

21
@Evgeny: хотя некоторые руководства по стилю JavaScript призывают открывать фигурные скобки для перехода на одну и ту же строку, неправильно вводить их в новую строку, и нет никакой опасности, что интерпретатор двусмысленно вставит точку с запятой. Поведение ASI хорошо определено и здесь не применимо.
Джейсон Суарес

9
Просто не забудьте прокомментировать этот подход. Не сразу очевидно, что здесь происходит.
Qix - МОНИКА БЫЛА ПОВРЕЖДЕНА

1
Хороший и простой ответ. Это следует рассматривать как ответ, так как он не напрягает интенсивные циклы ЦП (что является проблемой при использовании функций) или не использует метки, которые обычно не читаются или не должны использоваться, как говорят некоторые. :)
Гириш Сортур

2
Возможно, я что-то упускаю, но чтобы обойти проблему внутреннего цикла, необходимого для завершения этой итерации, вы могли бы вставить a breakили continueсразу после установки z и y? Мне действительно нравится идея использовать forусловия цикла, чтобы выкинуть. Элегантно по-своему.
Бен Саттон

76

Я понимаю, что это действительно старая тема, но поскольку моего стандартного подхода еще нет, я решил опубликовать его для будущих гуглеров.

var a, b, abort = false;
for (a = 0; a < 10 && !abort; a++) {
    for (b = 0; b < 10 && !abort; b++) {
        if (condition) {
            doSomeThing();
            abort = true;
        }
    }
}

2
Если результаты conditionвычисляются trueна первой итерации вложенного цикла, вы по-прежнему выполняете оставшиеся 10 итераций, abortкаждый раз проверяя значение. Это не проблема производительности для 10 итераций, но, скажем, с 10 000.
Робусто

7
Нет, он выходит из обеих петель. Вот демонстрационная скрипка . Независимо от того, какое условие вы установили, оно завершается после того, как оно выполнено.
zord

4
Оптимизация должна была бы добавить разрыв; после установки abort = true; и удаление! прервать проверку состояния из последнего цикла.
xer21

1
Мне это нравится, но я думаю, что в общем смысле вы бы сделали много ненужной обработки, то есть для каждой итерации каждого значения итератора abortи выражения. В простых сценариях, которые могут быть хорошими, но для огромных циклов с повторениями в gazillion, которые могут быть проблемой
З. Хулла

1
Ребята, вы действительно спорите о том, является ли проверка единственного логического значения 10000 раз быстрой или медленной? попробуйте 100 миллионов раз / вздох
fabspro

40
var str = "";
for (var x = 0; x < 3; x++) {
    (function() {  // here's an anonymous function
        for (var y = 0; y < 3; y++) {
            for (var z = 0; z < 3; z++) {
                // you have access to 'x' because of closures
                str += "x=" + x + "  y=" + y + "  z=" + z + "<br />";
                if (x == z && z == 2) {
                    return;
                }
            }
        }
    })();  // here, you execute your anonymous function
}

Как это? :)


2
Я понял, что это то, к чему стремятся свилиамы
harley.333

18
Это добавляет значительные затраты времени выполнения, если цикл большой - новый контекст выполнения для функции должен быть создан (и в какой-то момент освобожден GC) интерпретатором / компилятором Javascript (или, в наши дни, «compreter», смесью обоих) КАЖДЫЙ РАЗ.
Мёрре

2
Это на самом деле довольно опасно, потому что может случиться что-то странное, чего вы не ожидаете. В частности, из-за замыкания, созданного с помощью var x, если какая-либо логика в цикле ссылается на x в более поздний момент времени (например, он определяет внутреннюю анонимную функцию, которая сохраняется и выполняется позже), значение для x будет любым был в конце цикла, а не индекс, который функция была определена во время. (продолжение)
devios1

1
Чтобы обойти это, вам нужно передать xв качестве параметра вашу анонимную функцию, чтобы она создала новую копию, на которую затем можно ссылаться как на закрытие, поскольку она не изменится с этого момента. Короче, я рекомендую ответ ephemient.
devios1

2
Кроме того, я думаю, что удобочитаемость - полная чушь. Это более расплывчато, чем ярлык. Метки считаются нечитаемыми только потому, что их никто не использует.
Qix - МОНИКА БЫЛА ПОВРЕЖДЕНА

39

Достаточно просто:

var a = [1, 2, 3];
var b = [4, 5, 6];
var breakCheck1 = false;

for (var i in a) {
    for (var j in b) {
        breakCheck1 = true;
        break;
    }
    if (breakCheck1) break;
}

Я согласен, что на самом деле это лучшая функция, которую нельзя масштабировать, оборачивая все для циклов, если она также не масштабируется, то есть затрудняет чтение и отладку ... это потрясающе. Вы можете просто объявить vars loop1, loop2, loop3 и добавить небольшой оператор в конце. Кроме того, чтобы разорвать несколько циклов, вам нужно сделать что-то вродеloop1=loop2=false;
Мухаммед Умер

22

Вот пять способов вырваться из вложенных циклов в JavaScript:

1) Установить родительский (ые) цикл до конца

for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
        {
            i = 5;
            break;
        }
    }
}

2) Используйте этикетку

exit_loops:
for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
            break exit_loops;
    }
}

3) Используйте переменную

var exit_loops = false;
for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
        {
            exit_loops = true;
            break;
        }
    }
    if (exit_loops)
        break;
}

4) Используйте функцию самовоспроизведения

(function()
{
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
             if (j === 2)
                 return;
        }
    }
})();

5) Используйте обычную функцию

function nested_loops()
{
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
             if (j === 2)
                 return;
        }
    }
}
nested_loops();

1
@ Не могу согласиться! Это позор, JavaScript не просто имеет синтаксис, break 2;как у нас в PHP. Нет меток циклов, нет функций, нет проверок if-else, нет закалки с / удаления переменных цикла - просто чистый синтаксис!
Джей Дадхания

1
Пример 4
изящен

14

Как насчет использования без разрывов, без флагов отмены и без дополнительных проверок условий. Эта версия просто взрывает переменные цикла (делает их Number.MAX_VALUE), когда условие выполняется, и принудительно завершает все циклы.

// No breaks needed
for (var i = 0; i < 10; i++) {
  for (var j = 0; j < 10; j++) {
    if (condition) {
      console.log("condition met");
      i = j = Number.MAX_VALUE; // Blast the loop variables
    }
  }
}

Был найден аналогичный ответ для вложенных циклов убывающего типа, но он работает для вложенных циклов инкрементного типа без необходимости учитывать значение завершения каждого цикла для простых циклов.

Другой пример:

// No breaks needed
for (var i = 0; i < 89; i++) {
  for (var j = 0; j < 1002; j++) {
    for (var k = 0; k < 16; k++) {
      for (var l = 0; l < 2382; l++) {
        if (condition) {
          console.log("condition met");
          i = j = k = l = Number.MAX_VALUE; // Blast the loop variables
        }
      }
    }
  }
}

4

Как насчет подталкивания петель к их предельным пределам?

    for(var a=0; a<data_a.length; a++){
       for(var b=0; b<data_b.length; b++){
           for(var c=0; c<data_c.length; c++){
              for(var d=0; d<data_d.length; d++){
                 a =  data_a.length;
                 b =  data_b.length;
                 c =  data_b.length;
                 d =  data_d.length;
            }
         }
       }
     }

1
Я думаю, что ответ Дрейкса имеет ту же логику в более краткой и ясной манере.
Инженер Тост

абсолютно блестящий!
geoyws

3

Если вы используете Coffeescript, есть удобное ключевое слово «do», которое упрощает определение и немедленное выполнение анонимной функции:

do ->
  for a in first_loop
    for b in second_loop
      if condition(...)
        return

... так что вы можете просто использовать "возврат", чтобы выйти из циклов.


Это не то же самое. Мой оригинальный пример имеет три forцикла, а не два.
Гэри Уиллоуби

2

Я думал, что покажу функционально-программный подход. Вы можете отказаться от вложенных функций Array.prototype.some () и / или Array.prototype.every (), как в моих решениях. Дополнительным преимуществом этого подхода является то, что он Object.keys()перечисляет только собственные перечисляемые свойства объекта, тогда как «цикл for-in перечисляет свойства и в цепочке прототипов» .

Близко к решению ОП:

    Args.forEach(function (arg) {
        // This guard is not necessary,
        // since writing an empty string to document would not change it.
        if (!getAnchorTag(arg))
            return;

        document.write(getAnchorTag(arg));
    });

    function getAnchorTag (name) {
        var res = '';

        Object.keys(Navigation.Headings).some(function (Heading) {
            return Object.keys(Navigation.Headings[Heading]).some(function (Item) {
                if (name == Navigation.Headings[Heading][Item].Name) {
                    res = ("<a href=\""
                                 + Navigation.Headings[Heading][Item].URL + "\">"
                                 + Navigation.Headings[Heading][Item].Name + "</a> : ");
                    return true;
                }
            });
        });

        return res;
    }

Решение, которое сокращает итерацию по заголовкам / элементам:

    var remainingArgs = Args.slice(0);

    Object.keys(Navigation.Headings).some(function (Heading) {
        return Object.keys(Navigation.Headings[Heading]).some(function (Item) {
            var i = remainingArgs.indexOf(Navigation.Headings[Heading][Item].Name);

            if (i === -1)
                return;

            document.write("<a href=\""
                                         + Navigation.Headings[Heading][Item].URL + "\">"
                                         + Navigation.Headings[Heading][Item].Name + "</a> : ");
            remainingArgs.splice(i, 1);

            if (remainingArgs.length === 0)
                return true;
            }
        });
    });

2

Уже упоминалось ранее swilliams , но с примером ниже (Javascript):

// Function wrapping inner for loop
function CriteriaMatch(record, criteria) {
  for (var k in criteria) {
    if (!(k in record))
      return false;

    if (record[k] != criteria[k])
      return false;
  }

  return true;
}

// Outer for loop implementing continue if inner for loop returns false
var result = [];

for (var i = 0; i < _table.length; i++) {
  var r = _table[i];

  if (!CriteriaMatch(r[i], criteria))
    continue;

  result.add(r);
}

0

Хм привет на вечеринку 10 лет?

Почему бы не поставить какое-то условие в вашу пользу?

var condition = true
for (var i = 0 ; i < Args.length && condition ; i++) {
    for (var j = 0 ; j < Args[i].length && condition ; j++) {
        if (Args[i].obj[j] == "[condition]") {
            condition = false
        }
    }
}

Как это вы останавливаетесь, когда хотите

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

Args.some((listObj) => {
    return listObj.some((obj) => {
        return !(obj == "[condition]")
    })
})

Таким образом, цикл останавливается сразу после выполнения условия

Напоминание: этот код выполняется в TypeScript


-3
XXX.Validation = function() {
    var ok = false;
loop:
    do {
        for (...) {
            while (...) {
                if (...) {
                    break loop; // Exist the outermost do-while loop
                }
                if (...) {
                    continue; // skips current iteration in the while loop
                }
            }
        }
        if (...) {
            break loop;
        }
        if (...) {
            break loop;
        }
        if (...) {
            break loop;
        }
        if (...) {
            break loop;
        }
        ok = true;
        break;
    } while(true);
    CleanupAndCallbackBeforeReturning(ok);
    return ok;
};

9
Это выглядит более запутанным, чем оригинал.
Криштиану Фонтес

21
Как постмодернистское стихотворение
Digerkam

Проголосовал, потому что время от времени становится более подходящим для этого типа сценария (в большинстве случаев).
Коди

-4

лучший способ -
1) Сортировать оба массива, которые используются в первом и втором цикле.
2) если элемент соответствует, разорвать внутренний цикл и удерживать значение индекса.
3) при запуске следующей итерации запускают внутренний цикл со значением удержания индекса.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.