Jaki jest najlepszy sposób na zerwanie z zagnieżdżonych pętli w JavaScript?


448

Jaki jest najlepszy sposób na oderwanie się od zagnieżdżonych pętli w 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.
         }
      }
   }
}

Oto dobry przykład przełamywania pętli i bloków kodu: marcin-chwedczuk.github.io/…
csharpfolk

Odpowiedzi:


1032

Podobnie jak 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
            }
        }
    }

zgodnie z definicją w EMCA-262 sekcja 12.12. [Dokumenty MDN]

W przeciwieństwie do C, te etykiety mogą być używane tylko dla continuei break, ponieważ JavaScript nie ma goto.


387
WTF, dlaczego nie widziałem tego używanego gdzieś w ciągu 3 lat z JavaScript: / ..
Salman von Abbas

39
MDN mówi „unikaj używania etykiet” wyłącznie ze względu na czytelność. Dlaczego nie jest to „czytelne”? Ponieważ nikt ich oczywiście nie używa. Ale dlaczego ich nie używają? ...
XML

7
@Web_Designer Uważam, że Twój komentarz jest nieaktualny. Nigdzie w dokumentach MDN nie jest powiedziane, aby „unikać używania etykiet”. Rozważ zmianę lub usunięcie komentarza.
Sean the Bean

8
@SeantheBean Done. To wydaje się być prostszą odpowiedzią i nie jest podatne na nadużycia, ponieważ jest dostępne tylko dla continuei break.
Gary Willoughby,

29
@ JérémyPouyet - Twoja logika głosowania w dół jest bezsensowna i nieuzasadniona. Doskonale odpowiada na pytanie PO. Pytanie nie dotyczy twoich opinii dotyczących czytelności. Zastanów się ponownie nad podejściem do pomocy społeczności.
The Dembinski

168

Zawiń to w funkcję, a potem po prostu return.


12
Zdecydowałem się zaakceptować tę odpowiedź, ponieważ jest ona prosta i może być realizowana w elegancki sposób. Absolutnie nienawidzę GOTO i uważam je za złą praktykę ( może się otworzyć ), Ephemient's jest zbyt blisko jednego. ; o)
Gary Willoughby,

16
IMO, GOTO są w porządku, o ile nie psują struktury. Ale każdemu z nich!
ephemient

31
Etykiety dla pętli nie mają absolutnie nic wspólnego z GOTO, z wyjątkiem ich składni. Są po prostu kwestią zerwania z zewnętrznymi pętlami. Nie masz problemu z przerwaniem najbardziej wewnętrznej pętli, prawda? więc dlaczego masz problem z zerwaniem zewnętrznych pętli?
John Smith

11
Proszę rozważyć przyjęcie innej odpowiedzi. Gdyby nie komentarz Andrew Hedgesa (dzięki btw.), Pomyślałbym: ah, więc javascript nie ma tej funkcji. Założę się, że wielu w społeczności może przeoczyć komentarz i myśleć tak samo.
John Smith

11
Dlaczego przepełnienie stosu nie ma funkcji, która pozwala społeczności zastąpić oczywiście złą odpowiedź? : /
Matt Huggins

85

Jestem trochę spóźniony na imprezę, ale poniższe podejście jest niezależne od języka, które nie używa GOTO / etykiet ani pakowania funkcji:

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

Z drugiej strony płynie naturalnie, co powinno zadowolić tłum nie-GOTO. Z drugiej strony, wewnętrzna pętla musi zakończyć bieżącą iterację przed zakończeniem, więc może nie mieć zastosowania w niektórych scenariuszach.


2
nawias otwierający nie powinien znajdować się w nowych liniach, ponieważ implementacje js mogą wstawić dwukropek na końcu poprzedniej linii.
Evgeny,

21
@Evgeny: chociaż niektóre przewodniki w stylu JavaScript wymagają otwierania nawiasów klamrowych, aby przejść do tej samej linii, nie jest błędem umieszczenie go w nowej linii i nie ma niebezpieczeństwa, że ​​interpreter niejednoznacznie wstawi średnik. Zachowanie ASI jest dobrze określone i nie ma tutaj zastosowania.
Jason Suárez,

9
Upewnij się, że skomentujesz to podejście. Nie wiadomo od razu, co się tutaj dzieje.
Qix - MONICA MISTREATED

1
Ładna i prosta odpowiedź. Powinno to być traktowane jako odpowiedź, ponieważ nie obciąża intensywnych pętli procesora (co jest problemem przy korzystaniu z funkcji) lub nie używa etykiet, które zwykle nie są czytelne lub nie powinny być używane, jak mówią niektórzy. :)
Girish Sortur

2
Być może czegoś mi brakuje, ale aby obejść problem związany z pętlą wewnętrzną, która musi zakończyć tę iterację, możesz wstawić a breaklub continuezaraz po ustawieniu Z i Y? Podoba mi się pomysł użycia warunków forpętli do wyrzucenia. Elegancki na swój sposób.
Ben Sutton,

76

Zdaję sobie sprawę, że to naprawdę stary temat, ale ponieważ moje standardowe podejście jeszcze nie jest dostępne, pomyślałem, że opublikuję go dla przyszłych pracowników Google.

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
Jeśli conditionwartość zostanie obliczona trueprzy pierwszej iteracji zagnieżdżonej pętli, nadal przeprowadzane jest przez pozostałe 10 iteracji, sprawdzając abortwartość za każdym razem. Nie jest to problem z wydajnością dla 10 iteracji, ale byłoby powiedzmy z 10 000.
Robusto,

7
Nie, wychodzi z obu pętli. Oto skrzypce demonstracyjne . Bez względu na ustawiony warunek, kończy się po spełnieniu.
zord

4
Optymalizacja polegałaby na dodaniu przerwy; po ustawieniu abort = true; i usunięcie! abort sprawdź warunek z ostatniej pętli.
xer21

1
Podoba mi się to, ale myślę, że w ogólnym sensie zrobiłbyś dużo niepotrzebnego przetwarzania - to znaczy dla każdej iteracji każdego z ewaluatorów aborti wyrażeń. W prostych scenariuszach, które mogą być w porządku, ale dla dużych pętli z iteracjami gazillionów może to stanowić problem
Z. Khullah

1
Czy naprawdę się spieracie, czy sprawdzenie pojedynczej wartości boolowskiej 10000 razy jest szybkie czy wolne? spróbuj 100 milionów razy / westchnienie
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
}

Jak to? :)


2
Doszedłem do wniosku, że właśnie o to chodziło swilliams
harley.333

18
Dodaje to znaczne koszty w czasie wykonywania, jeśli pętla jest duża - nowy kontekst wykonania dla funkcji musi zostać utworzony (i w pewnym momencie uwolniony przez GC) za pomocą interpretera / kompilatora Javascript (lub, w dzisiejszych czasach, „compreter”, połączenie obu) ZA KAŻDYM RAZEM.
Mörre

2
Jest to w rzeczywistości dość niebezpieczne, ponieważ mogą się zdarzyć dziwne rzeczy, których możesz się nie spodziewać. W szczególności, ze względu na zamknięcie utworzone za pomocą var x, jeśli jakakolwiek logika w pętli odwołuje się do x w późniejszym czasie (na przykład definiuje wewnętrzną anonimową funkcję, która jest zapisywana i wykonywana później), wartość x będzie dowolna znajdował się na końcu pętli, a nie indeks, w którym funkcja została zdefiniowana. (kont.)
devios1

1
Aby obejść ten problem, musisz przekazać xparametr do swojej anonimowej funkcji, aby utworzyła nową jej kopię, którą można następnie nazwać zamknięciem, ponieważ od tego momentu nie zmieni się. Krótko mówiąc, polecam odpowiedź ephemient.
devios1

2
Myślę też, że kwestia czytelności to bzdura. Jest to o wiele bardziej niejasne niż etykieta. Etykiety są postrzegane tylko jako nieczytelne, ponieważ nikt ich nigdy nie używa.
Qix - MONICA MISTREATED

39

Całkiem proste:

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

Zgadzam się, że jest to właściwie najlepsza funkcja, której nie można skalować, owijanie wszystkich pętli, jeśli również nie jest skalowana, tj. Utrudnia czytanie i debugowanie ... ta jest niesamowita. Możesz po prostu zadeklarować vars loop1, loop2, loop3 i dodać małą instrukcję na końcu. Aby przerwać wiele pętli, musisz zrobić coś takiegoloop1=loop2=false;
Muhammad Umer,

22

Oto pięć sposobów na wyrwanie się z zagnieżdżonych pętli w JavaScript:

1) Ustaw pętlę rodzica (rodziców) na końcu

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

2) Użyj etykiety

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

3) Użyj zmiennej

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) Użyj funkcji samodzielnego wykonywania

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

5) Użyj zwykłej funkcji

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

1
@ Wyck Nie mogę się wystarczająco zgodzić! Szkoda, że ​​javascript nie ma po prostu składni, break 2;jak w PHP. Bez etykiet pętli, bez funkcji, bez sprawdzania „if-else”, bez temperowania za pomocą / wybuchu zmiennych pętli - po prostu czysta składnia!
Jay Dadhania,

1
Przykład 4 jest fajny
leroyjenkinss24

14

Co powiesz na to, by nie używać żadnych przerw, żadnych flag przerwania i żadnych dodatkowych kontroli warunków. Ta wersja po prostu wysadza zmienne w pętli (czyni je Number.MAX_VALUE), gdy warunek jest spełniony, i zmusza wszystkie pętle do eleganckiego zakończenia.

// 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
    }
  }
}

Była podobna odpowiedź na zagnieżdżone pętle typu zmniejszającego, ale działa to na zagnieżdżone pętle typu bez konieczności rozważania wartości końcowej każdej pętli dla prostych pętli.

Inny przykład:

// 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

Co powiesz na przesunięcie pętli do ich granic końcowych

    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
Myślę, że odpowiedź Drakesa ma tę samą logikę w bardziej zwięzły i jasny sposób.
Engineer Toast,

absolutnie genialne!
geoyws

3

Jeśli używasz Coffeescript, istnieje wygodne słowo kluczowe „do”, które ułatwia zdefiniowanie i natychmiastowe wykonanie anonimowej funkcji:

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

... dzięki czemu możesz po prostu użyć „return”, aby wyjść z pętli.


To nie to samo. Mój oryginalny przykład ma trzy forpętle, a nie dwie.
Gary Willoughby

2

Myślałem, że pokażę podejście programowania funkcjonalnego. Możesz wyrwać się z zagnieżdżonych funkcji Array.prototype.some () i / lub Array.prototype.every (), jak w moich rozwiązaniach. Dodatkową zaletą tego podejścia jest to, że Object.keys()wylicza tylko własne wyliczalne właściwości obiektu, podczas gdy „pętla for-in wylicza również właściwości w łańcuchu prototypów” .

Blisko rozwiązania OP:

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

Rozwiązanie, które zmniejsza iterację nagłówków / pozycji:

    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

Wspomniane już wcześniej przez swilliams , ale z poniższym przykładem (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

Hmmm cześć na 10-letniej imprezie?

Dlaczego nie postawić jakiegoś warunku?

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

W ten sposób zatrzymujesz się, kiedy chcesz

W moim przypadku, używając Typescript, możemy użyć niektórych (), które przechodzą przez tablicę i zatrzymują się, gdy warunek jest spełniony, więc mój kod wygląda następująco:

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

W ten sposób pętla zatrzymała się zaraz po spełnieniu warunku

Przypomnienie: ten kod działa w 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
Wygląda to bardziej myląco niż oryginał.
Cristiano Fontes,

21
Jak postmodernistyczny wiersz
Digerkam

Zagłosowano, ponieważ czas oczekiwania staje się bardziej zbliżony do tego rodzaju scenariusza (w większości przypadków).
Cody

-4

najlepszym sposobem jest -
1) Posortuj obie tablice, które są używane w pierwszej i drugiej pętli.
2) jeśli element pasuje, przerwij wewnętrzną pętlę i przytrzymaj wartość indeksu.
3) przy rozpoczęciu następnej iteracji rozpocznij wewnętrzną pętlę z wartością indeksu wstrzymania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.