Pętle w Javascript

Pętle to konstrukcje programistyczne pozwalające powtarzać wykonanie pewnego fragmentu kodu wielokrotnie, dopóki spełniony jest określony warunek lub przez określoną liczbę razy. Zamiast pisać wiele razy to samo polecenie, możemy użyć pętli, aby automatycznie przechodzić przez kolejne iteracje. Pętle są niezastąpione, gdy mamy do przetworzenia sekwencję danych (np. elementy tablicy) albo gdy chcemy wykonać daną operację n razy. W JavaScript występuje kilka rodzajów pętli: for, while, do...while, a także specjalne konstrukcje do iteracji po kolekcjach, takie jak for...of i for...in. W tej części omówimy, jak działają poszczególne rodzaje pętli i jak ich używać.

Pętla for

Pętla for jest często używana, gdy z góry wiadomo, ile razy dany blok kodu ma zostać powtórzony, lub gdy chcemy iterować po kolejnych wartościach licznikowych (np. od 1 do 100). Charakteryzuje się ona umieszczeniem w nagłówku pętli wszystkich informacji sterujących: inicjalizacji, warunku kontynuacji oraz modyfikacji licznika po każdej iteracji. Składnia pętli for wygląda następująco:

 for (inicjalizacja; warunek; iteracja) {
     // kod, który będzie powtarzany dopóki warunek jest spełniony
 } 
  • Inicjalizacja: wykonywana jednorazowo na początku pętli. Często służy do utworzenia i zainicjalizowania zmiennej sterującej (licznika). Np. let i = 0.
  • Warunek (kontynuacji): jest sprawdzany przed każdą iteracją pętli. Dopóki ten warunek jest true, pętla będzie się wykonywać. Gdy tylko warunek stanie się false, pętla kończy działanie i program przechodzi do kolejnej części kodu za pętlą.
  • Iteracja (zmiana): to wyrażenie wykonywane po każdej iteracji (na końcu bloku pętli, tuż przed ponownym sprawdzeniem warunku). Służy najczęściej do zmiany wartości licznika pętli, np. i++ (zwiększenie licznika o 1).

Wszystkie trzy części są opcjonalne, ale średniki ; oddzielające je muszą pozostać. W praktyce najczęściej wypełniamy wszystkie te sekcje, a pominięcie którejś jest rzadziej spotykane i ma specyficzne zastosowania (np. nieskończone pętle). Standardowe użycie pętli for wygląda tak:

 for (let i = 1; i <= 5; i++) {
     console.log("Iteracja numer " + i);
 }
 console.log("Koniec pętli");

Przeanalizujmy powyższy przykład:

  • Inicjalizacja: let i = 1 – przed startem pętli tworzymy zmienną i i ustawiamy ją na 1. Zmienne sterujące pętli for bardzo często nazywa się i (od „index” lub „iterator”).
  • Warunek: i <= 5 – przed każdą iteracją sprawdzamy, czy i nie przekroczyło 5. Dopóki i wynosi 5 lub mniej, warunek jest prawdziwy i pętla się wykona. Gdy i stanie się 6, warunek i <= 5 będzie fałszywy i pętla zakończy działanie.
  • Iteracja: i++ – po wykonaniu bloku kodu za każdym razem zwiększamy i o 1.

Działanie krok po kroku:

  1. Ustaw i = 1 (inicjalizacja).
  2. Sprawdź warunek: czy i <= 5? (dla i=1 jest true, więc wchodzimy do pętli).
  3. Wewnątrz pętli wykonujemy console.log("Iteracja numer " + i). Dla i=1 wyświetli: „Iteracja numer 1”.
  4. Koniec bloku – wykonujemy krok iteracji i++ (teraz i staje się 2).
  5. Sprawdź warunek ponownie: i=2, czy 2 <= 5? true, więc kolejna iteracja.
  6. Wewnątrz pętli wypisze „Iteracja numer 2”.
  7. Koniec bloku – i++ (i = 3).
  8. Warunek: 3 <= 5? true, kontynuuj…
  9. … i tak dalej, aż do momentu gdy i zostanie zwiększone do 6.
  10. Gdy i = 6, sprawdzamy warunek: 6 <= 5? to już false – pętla się nie wykona dla i=6 i zostanie przerwana.
  11. Program przechodzi do instrukcji za pętlą, czyli wypisuje „Koniec pętli”.

W wyniku działania tego kodu zobaczymy kolejno w konsoli:

 Iteracja numer 1  
 Iteracja numer 2  
 Iteracja numer 3  
 Iteracja numer 4  
 Iteracja numer 5  
 Koniec pętli

Pętla for okazała się tu bardzo użyteczna – zamiast pisać pięć razy console.log z różnym numerem, napisaliśmy raz i kazaliśmy pętli powtórzyć to 5 razy, zmieniając za każdym razem wartość i. Jeśli chcielibyśmy wypisać od 1 do 100, wystarczyłoby zmienić warunek na i <= 100 i pętla automatycznie wykonałaby się 100 razy.

Zastosowania pętli for:

  • Iteracja po tablicach: Mając tablicę elementów, możemy użyć for z licznikiem jako indeksem, np.:
 let fruits = ["jabłko", "banan", "gruszka"]; 
 for (let index = 0; index < fruits.length; index++) { 
     console.log("Owoc nr " + index + ": " + fruits[index]); 
 }

Ten kod przejdzie przez wszystkie indeksy tablicy fruits (od 0 do fruits.length-1) i wypisze każdy owoc. Pętla for z licznikiem jest klasycznym sposobem przeglądania elementów tablicy.

  • Wykonywanie operacji określoną liczbę razy: np. obliczenie silni liczby, sumy ciągu liczb itp. Pętla for potrafi łatwo iterować zliczając coś.
  • Generowanie powtarzalnych wzorców wyjścia: np. narysowanie prostych figur złożonych z znaków (choć do tego często używa się pętli zagnieżdżonych).

Warto zauważyć, że wszystkie trzy części nagłówka pętli for są opcjonalne – możemy np. pominąć inicjalizację (jeśli zmienna była wcześniej zdefiniowana), pominąć iterację (jeśli zwiększamy licznik wewnątrz pętli inaczej) czy nawet pominąć warunek (co stworzy pętlę nieskończoną, wykonującą się wciąż, dopóki nie przerwiemy jej ręcznie, np. instrukcją break). Przykład pętli nieskończonej: for(;;) { ... } – wewnątrz musiałby być jakiś break żeby kiedykolwiek wyjść. Jednak w praktyce początkujący rzadko mają potrzebę pomijania tych sekcji – standardowa forma jest najbardziej czytelna.

Pętla while

Pętla while ma prostszą konstrukcję niż for, ale daje dużą elastyczność. Jej składnia to:

 while (warunek) {
     // kod powtarzany dopóki warunek jest true
 } 

Działanie: dopóki podany warunek jest spełniony (true), wykonuj w kółko blok kodu wewnątrz pętli. Sprawdzenie warunku następuje przed każdą iteracją (czyli jest to pętla z warunkiem na początku). Jeśli warunek jest fałszywy już na starcie, kod pętli nie wykona się ani razu.

Pętla while przypomina zatem nieco uproszczony for bez części inicjalizacji i iteracji w nagłówku – te elementy musimy zapewnić sobie sami w kodzie, jeśli są potrzebne. Zazwyczaj używamy while w sytuacjach, gdy nie wiemy z góry ile iteracji będzie potrzebnych, i pętla powinna trwać tak długo, aż coś się wydarzy (aż warunek przestanie być spełniony).

Przykład: Użyjemy pętli while, aby obliczyć najmniejszą potęgę liczby 2, która przekracza 100. Innymi słowy, będziemy mnożyć 2 * 2 * 2 * … aż wynik stanie się większy niż 100.

 let liczba = 1;
 let potega = 0;
 while (liczba <= 100) {
     liczba = liczba * 2;
     potega++;
 }
 console.log("2^" + potega + " = " + liczba + ", czyli to pierwsza potęga 2 > 100");

W tym kodzie:

  • Ustawiamy liczba = 1 (co odpowiada 2^0) i licznik potęgi potega = 0.
  • Warunek pętli to liczba <= 100. Dopóki wynik jest 100 lub mniej, musimy kontynuować mnożenie.
  • Wewnątrz pętli: mnożymy liczba przez 2 i zwiększamy potega o 1 (czyli liczymy następną potęgę).
  • Gdy warunek przestanie być spełniony (tzn. liczba przekroczy 100), wychodzimy z pętli i wypisujemy wynik.

Działanie krokowe:

  • Początek: liczba = 1, potega = 0. Sprawdzenie warunku: 1 <= 100 (true) -> wejdź do pętli.
  • W pętli: liczba = 1*2 = 2, potega = 1. Koniec iteracji.
  • Sprawdzenie warunku: 2 <= 100 (true) -> kolejna iteracja.
  • W pętli: liczba = 2*2 = 4, potega = 2.
  • Warunek: 4 <= 100 (true) -> dalej.
  • … (pętla będzie kolejno dawać liczba: 8,16,32,64,128; potega: 3,4,5,6,7)
  • Gdy liczba stanie się 128 i potega 7, sprawdzenie warunku: 128 <= 100 jest false, więc pętla kończy się.
  • Po pętli wypisujemy: „2^7 = 128, czyli to pierwsza potęga 2 > 100”.

Wynik pokazuje, że 27=1282^7 = 12827=128 jest pierwszą potęgą dwójki większą niż 100, co zgadza się z oczekiwaniami (bo 26=642^6 = 6426=64 jeszcze było poniżej 100). Widzimy tutaj typowy przypadek pętli while, gdzie nie było z góry ustalone ile razy pętla się wykona – kręciła się tak długo, aż osiągnięto pewien stan (liczba przekroczyła 100).

W pętli while bardzo ważne jest, by kod wewnątrz zmierzał do spełnienia warunku kończącego pętlę. Jeśli zapomnimy zmienić wartości, która jest sprawdzana, lub warunek nigdy nie stanie się fałszywy, dostaniemy pętlę nieskończoną. Np. while(true) { ... } będzie się wykonywać w nieskończoność (chyba że w środku użyjemy break – o tym za chwilę). W naszym przykładzie elementem zmierzającym do zakończenia pętli jest mnożenie liczba = liczba * 2 – dzięki temu liczba rośnie i w końcu przekroczy 100, przerywając pętlę. Gdybyśmy tego nie zrobili, utknęlibyśmy w nieskończonym loopie.

Pętle while często stosuje się również do czekania na jakiś warunek lub przetwarzania danych wejściowych, np. czytania kolejnych wartości aż do napotkania jakiejś specjalnej (choć w przeglądarkowym JavaScript typowe wejście/wyjście przebiega inaczej, więc to raczej koncepcyjny przykład).

Pętla do...while

Pętla do...while jest bardzo podobna do while, z tą różnicą, że warunek sprawdzany jest na końcu iteracji, a nie na początku. To powoduje, że pętla do...while zawsze wykona się przynajmniej raz, nawet jeśli warunek od początku jest fałszywy. Składnia wygląda tak:

 do {
     // kod do wykonania
 } while (warunek);

Zasada: wykonaj blok do { ... }, a następnie sprawdź while(warunek). Jeśli warunek jest true – wróć do początku pętli (czyli wykonaj blok ponownie), jeśli false – zakończ pętlę.

Przykład użycia do...while: Wyobraźmy sobie, że chcemy symulować rzut kostką do gry tak długo, aż wypadnie nam szóstka. Pętla powinna wykonać się co najmniej raz (bo musimy chociaż raz rzucić), a potem powtarzać, jeśli rezultat nie był 6.

 function rzutKostka() {
     // funkcja pomocnicza zwracająca losową liczbę od 1 do 6
     return Math.floor(Math.random() * 6) + 1;
 }

 let wynik;
 do {
     wynik = rzutKostka();
     console.log("Wynik rzutu: " + wynik);
 } while (wynik !== 6);
 console.log("Wypadła 6, koniec pętli.");

W tym kodzie:

  • Używamy funkcji Math.random() do wygenerowania pseudolosowej liczby i symulacji rzutu sześciościenną kostką.
  • Pętla do...while wykonuje blok co najmniej raz. W bloku do generujemy wynik rzutu i wypisujemy go.
  • Warunek pętli to wynik !== 6 (czyli dopóki wynik jest różny od 6, kontynuuj).
  • Jeśli wylosowana liczba jest różna od 6, pętla powtarza się i kostka rzucana jest ponownie. Gdy w końcu wynik stanie się równy 6, warunek wynik !== 6 zwróci false i pętla się zakończy.
  • Po pętli wypisujemy informację o zakończeniu.

Wykonanie takiego programu może skutkować np. następującym ciągiem w konsoli:

Wynik rzutu: 4  
Wynik rzutu: 2
Wynik rzutu: 5
Wynik rzutu: 6
Wypadła 6, koniec pętli.

Tutaj pętla wykonała się 4 razy, ostatni rzut dał wynik 6 i spowodował zakończenie iteracji. Gdyby pierwszy rzut od razu dał 6, pętla i tak wykonałaby się ten jeden raz (wypisując wynik) i zakończyła, co pokazuje przewagę do...while w sytuacjach, gdzie zawsze chcemy wykonać ciało pętli przynajmniej raz, niezależnie od warunku.

Pętle for...in i for...of

Poza omówionymi wyżej podstawowymi konstrukcjami, JavaScript posiada również dwie specjalne pętle skracające iterowanie po obiektach lub kolekcjach danych:

  • for...in: służy do iteracji po właściwościach obiektu (kluczach) lub indeksach tablicy. Jego składnia to for (zmienna in obiekt) { ... }. Przy każdej iteracji zmienna przyjmuje kolejną nazwę własności (klucza) obiektu. Na tablicach zwróci indeksy (które w JS są właściwie także kluczami, tylko numerycznymi). Przykład:
 let person = { name: "Jan", age: 30, city: "Kraków" };  for (let key in person) { 
     console.log(key + ": " + person[key]); 
 }

Ten kod przejdzie po wszystkich kluczach obiektu person (czyli name, age, city) i wypisze nazwy pól oraz ich wartości. Rezultat: name: Jan age: 30 city: Kraków Pętla for...in jest więc wygodna do przeglądania właściwości obiektu. Można jej użyć też do iteracji po tablicy, ale zazwyczaj do tablic preferuje się pętlę for lub nowocześniejszą for...of, ponieważ for...in przy tablicach może iterować również ewentualne dodatkowe właściwości tablicy poza indeksami, jeśli takie istnieją (rzadki przypadek, ale bywa).

  • for...of: wprowadzona w nowszej wersji JavaScript (ES6) pętla służąca do iteracji bezpośrednio po elementach iterowalnych kolekcji, takich jak tablice, ciągi znaków (stringi), mapy, zbiory itp. Jej składnia to for (zmienna of iterowalnaStruktura) { ... }. W odróżnieniu od for...in, tutaj zmienna przyjmuje wartości elementów (a nie ich klucze/indeksy). Przykład użycia z tablicą:
 let fruits = ["Jabłko", "Banan", "Gruszka"]; 
 for (let fruit of fruits) { 
     console.log("Owoc: " + fruit); 
 }

Wynik działania:

Owoc: Jabłko Owoc: Banan Owoc: Gruszka

Jak widać, pętla for...of uprościła kod – nie trzeba odwoływać się do tablicy przez indeks, jak to było w tradycyjnej pętli for. Podobnie możemy iterować po znakach w stringu:

 let word = "HELLO"; 
 for (let ch of word) { 
     console.log(ch); 
 }

To wypisze każdy znak z osobna: H E L L O Pętla for...of jest bardzo czytelna i zalecana, gdy chcemy po kolei przetworzyć elementy jakiejś kolekcji (i nie potrzebujemy indeksu elementu). Trzeba jednak pamiętać, że działa na tzw. obiektach iterowalnych – w praktyce najczęściej na tablicach i zbiorach danych. Nie można nią iterować po zwykłym obiekcie (nieiterowalnym) – do obiektów służy wspomniana for...in lub metody obiektu.

Sterowanie przebiegiem pętli: break i continue

Podczas używania pętli czasem potrzebujemy wpłynąć na jej działanie z wnętrza bloku, np. przerwać pętlę wcześniej lub pominąć bieżącą iterację. Służą do tego dwie specjalne instrukcje:

  • break: natychmiast przerywa wykonywanie całej pętli i powoduje wyjście z niej. Program kontynuuje wykonywanie kodu od pierwszej instrukcji za pętlą. break zazwyczaj używa się, gdy znaleźliśmy to, czego szukaliśmy i dalsze kręcenie pętli jest zbędne, lub w sytuacji jakiegoś błędu/wyjątku, gdy chcemy porzucić dalsze powtarzanie.
  • continue: przerywa bieżącą iterację pętli i przechodzi do kolejnej iteracji (o ile warunek pętli nadal jest spełniony). Kod znajdujący się poniżej continue w ciele pętli zostanie pominięty w danej iteracji, ale pętla nie kończy się całkowicie – zaczyna się następny obrót. continue jest użyteczne, gdy chcemy pominąć niektóre elementy i nie wykonywać dla nich całego bloku pętli.

Przykład użycia break: załóżmy, że mamy tablicę liczb i chcemy znaleźć pierwszą liczbę ujemną w tej tablicy. Gdy ją znajdziemy, nie ma sensu przeglądać dalszych elementów – możemy przerwać pętlę.

 let numbers = [3, 7, 2, -5, 8, -1, 4];
 let firstNegative = null;
 for (let num of numbers) {
     if (num < 0) {
         firstNegative = num;
         break;  // znaleziono pierwszą ujemną liczbę, przerywamy pętlę
     }
 }
 if (firstNegative !== null) {
     console.log("Pierwsza liczba ujemna to: " + firstNegative);
 } else {
     console.log("W tablicy nie było liczb ujemnych.");
 } 

Analiza: Tablica numbers zawiera kilka liczb, w tym ujemne. Pętla for...of przechodzi po kolei przez liczby: 3, 7, 2, -5, … Kiedy natrafi na -5, warunek num < 0 staje się prawdą, więc ustawiamy zmienną firstNegative na tę wartość i wykonujemy break. break natychmiast kończy pętlę – w tym momencie przestajemy iterować, choć w tablicy były jeszcze dalsze elementy (-1, 4), to już nas nie obchodzi, bo szukaliśmy tylko pierwszej ujemnej. Po pętli sprawdzamy, czy znaleziono liczbę ujemną (zmienna firstNegative różna od null) i wypisujemy wynik. W tym przykładzie wypisze: „Pierwsza liczba ujemna to: -5”.

Przykład użycia continue: zademonstrujmy pominiecie pewnych iteracji. Np. chcemy wypisać liczby od 1 do 10, ale z adnotacją, które z nich są parzyste, a które nie. Można to zrobić na różne sposoby, w tym z continue:

 for (let i = 1; i <= 10; i++) {
     if (i % 2 == 0) {
         console.log(i + " jest parzyste");
         continue; // przejdź do kolejnej iteracji, nie wykonując dalszych instrukcji dla parzystych
     }
     console.log(i + " jest nieparzyste");
 }

Tutaj w każdej iteracji pętli for sprawdzamy, czy bieżąca liczba i jest parzysta (warunek i % 2 == 0). Jeśli tak, wypisujemy informację że jest parzysta i używamy continue. Instrukcja continue powoduje, że pętla od razu przechodzi do kolejnego obrotu, pomijając resztę kodu wewnątrz pętli dla aktualnej wartości i. W efekcie dla liczb parzystych nie wykona się linia console.log(i + " jest nieparzyste"), bo została pominięta. Dla nieparzystych warunek if będzie fałszywy, więc continue nie zadziała i program dojdzie do drugiej linijki, wypisując informację o nieparzystości.

Rezultat powyższego kodu:

1 jest nieparzyste  
2 jest parzyste
3 jest nieparzyste
4 jest parzyste
5 jest nieparzyste
6 jest parzyste
7 jest nieparzyste
8 jest parzyste
9 jest nieparzyste
10 jest parzyste

Jak widać, continue pozwoliło nam rozdzielić logikę dla parzystych i nieparzystych w jednej pętli, pomijając wykonanie niepotrzebnej części kodu w danej iteracji.

Podsumowanie pętli: Pętle for, while, do...while oraz konstrukcje for...in i for...of to potężne narzędzia pozwalające efektywnie powtarzać operacje. Należy zawsze dbać o to, by pętla miała odpowiedni warunek kończący i nie stała się nieskończona (chyba że celowo uruchamiamy nieskończoną pętlę w specyficznych scenariuszach, co jednak w codziennym programowaniu zdarza się rzadko i wymaga ostrożności). Użycie break i continue daje dodatkową kontrolę nad przebiegiem pętli – można przerwać całość lub tylko pominąć pojedynczą iterację według potrzeby.