Podstawy pętli w TypeScript: while, do-while, for, break i continue
Dowiedz się, w jaki sposób pętle while, do-while i for w TypeScript oceniają swoje warunki, dlaczego zasięg zmiennej pętli wpływa na wyniki oraz jak instrukcje break i continue zmieniają bieg programu.
Źródła: Mastering Loops in TypeScript: while, do-while, for, break & continue
Pętle umożliwiają wielokrotne wykonywanie tego samego bloku kodu, dopóki zachodzi określona warunek, zamiast ciągłego kopiowania instrukcji. Wydają się proste, ale większość błędów w pętlach wynika z trzech aspektów: momentu sprawdzania warunku, miejsca przechowywania zmiennej pętli oraz pozycji instrukcji break lub continue w jej ciele. Ten przewodnik omawia pętle while, do-while i for w TypeScript z prostymi przykładami, których wyniki można przewidzieć krok po kroku, dzięki czemu można czytać i pisać kod iteracyjny bez niespodzianek.
Dlaczego iteracja jest lepsza od powtarzania
Drukowanie liczb od 1 do 10 bez użycia pętli wymaga dziesięciu wywołań console.log. Z pętlą opisuje się zadanie raz, a system je powtarza – rozmiar kodu pozostaje taki sam, niezależnie od tego, czy potrzebne są dziesięć, czy dziesięć tysięcy powtórzeń.
Pętla while: najpierw sprawdzenie, potem wykonywanie
Pętla while ma najprostszą strukturę ze wszystkich:
while (condition) {
// statements
}
Warunek jest oceniany przed każdym przetworzeniem. Dopóki ma wartość true, wykonywany jest jej korpus; gdy stanie się false, pętla się kończy. Aby pętla while działała, potrzeba trzech elementów:
- początkowej wartości dla zmiennej, od której zależy warunek;
- samego warunku;
- czegoś w korpusie pętli, co sprawia, że wartość tej zmiennej zmierza ku ustawieniu warunku na
false.
Jeśli pominie się trzeci element, pętla nigdy się nie zatrzyma – problem ten zostanie omówiony poniżej.
Liczenie od 1 do 10
Tutaj wszystkie trzy elementy są widoczne: i zaczyna od 1, pętla działa, dopóki i <= 10, a i++ zwiększa jej wartość przy każdym przetworzeniu:
let i: number = 1; // initialization
while (i <= 10) {
console.log(i);
i++;
}
Filtrowanie liczb parzystych
Aby wydrukować tylko liczby parzyste w tym zakresie, należy nadal przeglądać każdą wartość, ale dodać weryfikację wewnątrz pętli. Operator modulo % zwraca resztę dzielenia, więc i % 2 == 0 jest prawdziwe dokładnie wtedy, gdy i jest liczbą parzystą:
let i: number = 1;
while (i <= 10) {
if (i % 2 == 0) {
console.log(i);
}
i++;
}
Istnieje lżejszy sposób na osiągnięcie podobnego rezultatu: można całkowicie pominąć weryfikację i za każdym razem przesuwać się o dwa. Należy zauważyć, że ta wersja zaczyna się od 0, więc jej wynik obejmuje również 0:
let i: number = 0;
while (i <= 10) {
console.log(i);
i = i + 2;
}
Wynik: 0, 2, 4, 6, 8, 10
Filtrowanie liczb nieparzystych
Zmiana warunku na != 0 pozwala zamiast tego wybrać wartości nieparzyste. Zaczynanie od 0 nie stanowi tu problemu, ponieważ 0 jest liczbą parzystą i zostaje wyfiltrowane:
let i: number = 0;
while (i <= 10) {
if (i % 2 != 0) {
console.log(i);
}
i++;
}
Wynik: 1, 3, 5, 7, 9
W tych przykładach użyto == i !=; zazwyczaj preferowane są bardziej rygorystyczne formy === i !==, ponieważ nigdy nie dokonują konwersji typów, chociaż w przypadku dwóch liczb wynik jest identyczny.
Odgrywanie wstecz
Aby liczyć w dół, zacznij od wysokiej wartości i zmniejszaj ją, dopóki nie spadnie ona co najmniej do 1:
let i: number = 10;
while (i >= 1) {
console.log(i);
i--;
}
Pętle nieskończone i ich powstawanie
Warunek w pętli while to po prostu wyrażenie, które zwraca wartość logiczną, więc while (true) jest w pełni poprawne. Poniższa pętla będzie wyświetlać dane bez końca, ponieważ nic nie może jej zakończyć:
let i: number = 1;
while (true) {
console.log(i);
i++;
// no break, no exit condition this runs forever
}
Częściej pętle nieskończone powstają przypadkowo: zmienna sterująca nigdy nie jest aktualizowana lub zmienia wartość w niewłaściwym kierunku. Pętla wymykająca się spowalnia całą pracę wątku, przez co karta przeglądarki lub proces Node.js wydaje się zawieszony. Celowe użycie while (true) jest w porządku, jeśli połączone jest z wyraźnym warunkiem wyjścia, zwykle break (opisane poniżej), na przykład w logice „próbuj ponownie, aż się uda”.
Pętla do-while: uruchom raz, potem sprawdź
Pętla do-while umieszcza warunek na końcu:
do {
// statements
} while (condition);
Ponieważ sprawdzenie warunku następuje po ciele pętli, to ciało jest zawsze wykonywane przynajmniej raz, niezależnie od tego, jaki jest warunek. Zwykła pętla while natomiast może nie zostać wykonywana wcale.
Gdy warunek na początku jest prawdziwy, pętla do-while zachowuje się dokładnie tak samo jak pętla while:
let i: number = 1;
do {
console.log(i);
i++;
} while (i <= 5);
Różnica pojawia się, gdy warunek od samego początku jest fałszywy:
let i: number = 10;
do {
console.log(i);
i++;
} while (i <= 5);
Wynik: 10
Ciało pętli jest wykonywane bezwarunkowo, wyświetlając 10 i zwiększając wartość i do 11. Dopiero wtedy jest oceniany warunek i <= 5; wynosi on false, więc pętla kończy się po jednej iteracji. Pętla while (i <= 5) rozpoczynająca się od wartości 10 nie wyświetliłaby nic. Użyj struktury do-while, gdy konieczne jest wykonanie pętli przynajmniej raz, na przykład aby pokazać komunikat co najmniej jeden raz.
Pętla for: wszystko w jednym nagłówku
Pętla for jest tą, której będziesz używał najczęściej, szczególnie gdy z góry wiesz, ile razy należy ją powtórzyć lub o ile wartość licznika powinna się zmienić. Łączy ona operację inicjalizacji, warunek i aktualizację w jednym nagłówku, dzięki czemu jest bardziej zwięzła niż odpowiednia pętla while:
for (initialization; condition; increment/decrement) {
// statements
}
Kolejność, w której wykonywane są te elementy, warto zapamiętać:
- Inicjalizacja odbywa się raz, przed wszystkim innym (na przykład
let i = 1). - Oceniana jest warunek. Jeśli wynosi on
true, wykonywany jest ciało pętli; jeślifalse, pętla się kończy. - Po ciele pętli wykonywana jest instrukcja aktualizacji (na przykład
i++). - Kontrola wraca do kroku 2, a cykl powtarza się, dopóki warunek nie będzie
false.
Innymi słowy, inicjalizacja następuje tylko raz, natomiast warunek i aktualizacja odbywają się przy każdym cyklu.
Liczenie od 1 do 10
for (let i = 1; i <= 10; i++) {
console.log(i);
}
Dwa sposoby na uzyskanie liczb parzystych
Tak jak w przypadku while, można postępować co dwa kroki:
for (let i = 2; i <= 10; i += 2) {
console.log(i);
}
lub sprawdzać każdą wartość i filtrować je wewnątrz ciała pętli:
for (let i = 2; i <= 10; i++) {
if (i % 2 == 0) {
console.log(i);
}
}
Oba sposoby wyświetlają tę samą sekwencję:
2
4
6
8
10
Krok po kroku wykonuje mniej operacji, ponieważ sprawdza tylko wartości parzyste; filtrowanie jest bardziej elastyczne, gdy reguła nie polega na stałym kroku, np. „dzielny przez 3 lub 7”.
Odliczanie
Zmniejszanie wartości powoduje uruchomienie pętli w odwrotnej kolejności:
for (let i = 10; i >= 1; i--) {
console.log(i);
}
Wynik: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
Dlaczego zmienna pętli wynosi 6 po zakończeniu pętli
Miejsce deklaracji zmiennej pętli wpływa na to, co pozostaje po jej zakończeniu. W tym przykładzie i jest deklarowane przed pętlą, a blok for jedynie przypisuje do niej wartości:
let i: number;
for (i = 1; i <= 5; i++) {
console.log(i);
}
console.log(i);
Wynik: 1, 2, 3, 4, 5, 6
Ponieważ i znajduje się w zewnętrznym zakresie, ostatnia instrukcja console.log(i) nadal może do niej uzyskać dostęp i zachowuje wartość pozostawioną przez pętlę. Jest to 6, a nie 5, ze względu na kolejność operacji: podczas ostatniego przetworzenia i ma wartość 5, ciało pętli ją wyświetla, następnie aktualizacja podnosi tę wartość do 6, i dopiero wtedy warunek 6 <= 5 okazuje się fałszywy, co kończy pętlę.
Częściej używany styl, for (let i = 1; i <= 5; i++), nadaje i zakres blokowy ograniczony do samej pętli, więc po jej zakończeniu już nie istnieje. Odwoływanie się do niej po pętli powoduje błąd kompilacji w TypeScript („Cannot find name 'i'”), a w zwykłym JavaScript powodowałoby to błąd ReferenceError w czasie wykonywania. Zazwyczaj lepiej jest deklarować zmienną na początku; używaj deklaracji zewnętrznej tylko wtedy, gdy potrzebujesz jej końcowej wartości.
Zmiana przebiegu pętli za pomocą break i continue
Polecenia jump umożliwiają modyfikację normalnego biegu pętli bezpośrednio z jej wnętrza:
breaknatychmiast kończy pętlę, niezależnie od tego, ile iteracji jeszcze pozostało.continueprzerwa tylko bieżącą iterację i przechodzi do następnej; pętla nadal się uruchamia.
W pętli for polecenie continue nadal wykonywa wyrażenie aktualizacyjne przed ponowną weryfikacją warunku, dlatego licznik nadal rośnie.
break: miejsce wewnątrz ciała pętli ma znaczenie
Tutaj sprawdzenie break następuje przed wydrukiem:
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
console.log(i);
}
Wynik: 1, 2, 3, 4
Gdy i osiągnie wartość 5, pętla kończy się, zanim console.log zdąży zostać wywołany, dlatego liczba 5 nigdy się nie pojawia.
Gdy przenieś wydruk powyżej sprawdzenia, wynik się zmienia:
let i: number;
for (i = 1; i <= 10; i++) {
console.log(i);
if (i == 5) {
break;
}
}
Wynik: 1, 2, 3, 4, 5
Warunek pozostał niezmieniony, a mimo to pojawia się liczba 5, ponieważ polecenie wydruku jest wykonywane przed sprawdzeniem warunku. Podobnie jak w przypadku break i continue, kolejność instrukcji wewnątrz pętli decyduje o wyniku.
continue: pomijanie pojedynczej wartości
Zamiana break na continue zmienia „zatrzymanie się przy 5” na „pominięcie 5”:
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
continue;
}
console.log(i);
}
Wynik: 1, 2, 3, 4, 6, 7, 8, 9, 10
Pętla kontynuuje działanie; po prostu pomija wydruk dla tej jednej iteracji.
Pomijanie wszystkich liczb nieparzystych
W połączeniu z filtrem, continue kieruje każdą wartość nieparzystą bezpośrednio do następnej iteracji:
let i: number;
for (i = 0; i <= 10; i++) {
if (i % 2 != 0) {
continue;
}
console.log(i);
}
Wynik: 0, 2, 4, 6, 8, 10
Pomijanie listy określonych wartości
Warunek może być tak złożony, jak tego potrzebujesz. Korzystając z operatora logicznego OR ||, ten pętla pomija liczby 3, 5 i 7:
let i: number;
for (i = 0; i <= 10; i++) {
if (i == 3 || i == 5 || i == 7) {
continue;
}
console.log(i);
}
Wynik: 0, 1, 2, 4, 6, 8, 9, 10
Dla dłuższej listy do pominięcia lepiej sprawdzi się [3, 5, 7].includes(i) lub struktura Set.
Główne wnioski
- Pętla
whilesprawdza swój warunek przed każdym iteracją i może nie zostać wykonywana wcale; pętlado-whilesprawdza go po wykonaniu instrukcji i zawsze jest wykonywana co najmniej raz. - Każda pętla wymaga modyfikacji, która ostatecznie sprawi, że warunek stanie się fałszywy; w przeciwnym razie powstanie nieskończona pętla, która zablokuje wątek.
- W pętli
forinicjalizacja jest wykonywana raz, natomiast warunek i modyfikacja za każdym razem – dlatego licznik osiąga wartość większą o jeden od ustalonego limitu.
let w nagłówku pętli sprawia, że pozostaje on ograniczony do tej pętli; jego deklaracja poza nią umożliwia zachowanie jego końcowej wartości.break zakończa pętlę, a continue pomija jedną iterację; miejsce umieszczenia tych instrukcji względem innych zdań wpływa na wynik.Warunki decydują, który kod ma zostać wykonywany; pętle określają częstotliwość ich wykonywania. Razem obejmują one większość struktur sterowania, których będziesz używał.