Pętle w JavaScript

Cześć! To jest fragment książki JavaScript od podstaw, która ma pomóc w nauce programowania od zera.

Tak jak mówiliśmy w pierwszym rozdziale, każda linia kodu jest instrukcją. Te zaś wykonują się kolejno od góry do dołu.

console.log("To zostanie wypisane najpierw");
console.log("a to potem");

// Wypisze:
// To zostanie wypisane najpierw
// a to potem

Kolejność wykonywania instrukcji nazywamy przepływem sterowania. Jest to istotne, ponieważ zdarzają się pewne przypadki, które zmieniają ten przepływ poprzez przeskakiwanie z jednego w drugie miejsce. Jedną z nich już poznaliśmy — instrukcja if pozwala nam ominąć część kodu, gdy warunek nie jest spełniony. Do tej pory jednak nie byliśmy w stanie sprawić, by pojedyncze polecenie wykonało się więcej niż raz. Tę barierę przełamiemy dopiero teraz, a posłużą nam do tego właśnie pętle.

Pętla while

Definicja pętli while niewiele różni się od warunku if. Zmienia się tylko słowo kluczowe if na while. Zachowanie również jest do pewnego stopnia podobne.

Jeśli warunek nie jest spełniony (zwraca false), to ciało jest pomijane (podobnie jak w przypadku instrukcji warunkowej if).

while (false) {
  console.log("Ten tekst nigdy się nie wypisze");
}
if (false) {
  console.log("Ten tekst nigdy się nie wypisze");
}
// Nic się nie wypisze

Jeśli jednak warunek jest spełniony (zwraca true), to ciało zostanie wywołane. Tutaj while różni się od if tym, że warunek sprawdzamy ponownie, po czym, jeśli wciąż jest spełniony, to wywołujemy ciało raz jeszcze. Proces ten powtarzamy tak długo, aż warunek nie będzie spełniony.

W poniższym przypadku pierwszy warunek będzie spełniony tylko raz, bo w ciele zmieniamy wartość zmiennej toBePrinted na false. Drugi będzie spełniony trzykrotnie, bo w ciele zwiększamy wartość zmiennej, aż w końcu jej wartość będzie większa od 2.

let toBePrinted = true;
while (toBePrinted) {
  console.log("Będzie wypisane tylko raz");
  toBePrinted = false;
}
// Wypisze:
// Będzie wypisane tylko raz

let printedTimes = 0;
while (printedTimes <= 2) {
  console.log("Wypisane dla " + printedTimes);
  printedTimes += 1;
  // czyli inaczej printedTimes = printedTimes + 1
}
// Wypisze:
// Wypisane dla  0
// Wypisane dla  1
// Wypisane dla  2

Wracając do historii Jasia, można użyć pętli, aby oddać polecenie jego mamy: "Będziesz pisał wypracowanie, dopóki go nie skończysz".

while (!esseyFinished) {
  writeEssey();
}

Jeśli w pętli umieścilibyśmy warunek, który zawsze jest spełniony, pętla ta będzie wykonywana w nieskończoność. Poniższy program jest więc najprostszym sposobem na zablokowanie przeglądarki, ponieważ nieustannie będzie wypisywał do konsoli ten sam tekst.

// Robisz to na własną odpowiedzialność
while (true) {
  console.log("LOL");
}

Oznacza to, że ciało pętli może zostać wykonane od zera aż do nieskończonej liczby powtórzeń. Pętla, która nigdy się nie kończy, nazywana jest pętlą nieskończoną. W niektórych językach programowania jest ona wykorzystywana. W JavaScript częściej jest jednak wynikiem błędu programisty.

Typowa sytuacja użycia pętli ma miejsce, gdy w ciele poszerzamy jej wartość, aż będzie ona większa od ograniczenia górnego. Tak tworzy się znane nam ze szkoły ciągi liczbowe. Jeśli chcielibyśmy wypisać kolejne wielokrotności cyfry 3 (gdzie kolejny element powstaje poprzez dodanie liczby 3 do poprzedniej), moglibyśmy użyć następującej pętli:

let i = 3;
while (i <= 10) {
  console.log(i);
  i += 3; // albo i = i + 3;
}

// Wypisze:
// 3
// 6
// 9

Uwaga! Jeśli wywołamy taki kod w konsoli, zobaczymy dodatkową 12 na końcu. Program zadziałał poprawnie. Dodatkowa wartość jest wartością zwracaną przez kod (czyli przez pętlę while). Jak dodasz jakąś dodatkową wartość po pętli, 12 już się nie pojawi. Wartość będącą wynikiem wprowadzonego kodu, a nie wypisania przez console.log poznasz po dodatkowej strzałce wyświetlonej po jej lewej stronie.

Jeśli chcielibyśmy wypisać ciąg kolejnych potęg liczby 2 (gdzie kolejny element powstaje poprzez podwojenie kolejnego), moglibyśmy użyć następującej pętli:

let i = 1;
while (i <= 100) {
  console.log(i);
  i *= 2; // albo i = i * 2;
}

// Wypisze:
// 1
// 2
// 4
// 8
// 16
// 32
// 64

Ćwiczenie: Ciągi matematyczne

  • Wypisz kolejne liczby parzyste (wielokrotności 2), zaczynając od 0, a mniejsze od 100.
  • Wypisz kolejne wielokrotności 7, zaczynając od 0, a mniejsze od 100.
  • Wyświetl kolejne wartości powstałe w wyniku wielokrotnego potrajania liczby, zaczynając od 13, tak długo, jak długo wynik jest mniejszy niż 1000.
  • Wypisz kwadraty kolejnych liczb całkowitych mniejszych od 1000.

Odpowiedzi na końcu książki.

Operatory inkrementacji ++ i dekrementacji --

Gdy używamy pętli, częstą operacją jest dodanie 1 do wartości zmiennej. Można to zrobić poprzez variable = variable + 1 albo variable += 1. Widzieliśmy to już w przedstawionym wcześniej przykładzie dla printedTimes. Z uwagi na częstotliwość tej czynności, programiści stworzyli specjalny operator do dodawania 11. Jest to tzw. operator inkrementacji, który zapisuje się jako ++. Tak więc variable += 1 albo variable = variable + 1 możemy zamienić na variable++.

i++
// to alternatywa dla i += 1
// to alternatywa dla i = i + 1

Operator inkrementacji często wykorzystuje się, gdy używamy pętli.

let printedTimes = 0;
while (printedTimes <= 2) {
   console.log("Wypisane dla " + printedTimes);
   printedTimes++;
}
// Wypisze:
// Wypisane dla  0
// Wypisane dla  1
// Wypisane dla  2

Analogicznie, odjęcie 1 od wartości zmiennej można zrobić poprzez użycie operatora dekrementacji --. Tak więc variable = variable — 1 albo variable -= 1 możemy zamienić na variable--.

let n = 4;
while (n >= 2) {
   console.log("Wypisane dla " + n);
   n--;
}
// Wypisze:
// Wypisane dla  4
// Wypisane dla  3
// Wypisane dla  2

Zarówno operator inkrementacji ++ jak i dekrementacji -- możemy umieścić przed zmienną (++variable, --variable), jak i po zmiennej (variable++, variable--). Różnica między nimi wybiega poza zakres tej książki, ale zainteresowanym polecam poczytać o postinkrementacji i preinkrementacji (oraz postdekrementacji i predekrementacji).

Pętla for

Pętle często wykorzystuje się, aby wykonać jakąś operację dla kolejnych liczb. Przy użyciu pętli while kolejne cyfry od 0 do 4 mogą być wyświetlone w następujący sposób:

let i = 0;
while (i <= 4) {
  // Ciało pętli, na przykład:
  console.log(i);
  i++; // To samo co i = i + 1
}

// Wypisze:
// 0
// 1
// 2
// 3
// 4

Aby uprościć ten wzorzec, wymyślona została pętla for, która zachowuje się bardzo podobnie, ale dostarcza w jednym miejscu przestrzeń na:

  • zdefiniowanie zmiennej określającej aktualną liczbę (np. let i = 0),
  • określenie, kiedy pętla powinna się zakończyć (np. i <= 4),
  • określenie, jak powinna być modyfikowana wartość tej zmiennej (np. i++).

Wymienione elementy to kolejne części wewnątrz nawiasów w pętli for. Oddzielamy je od siebie średnikami, natomiast cała reszta wygląda tak samo, jak w pętli while.

Poniżej wykorzystujemy pętlę for do wypisania kolejnych liczb od 0 do 4.

for (let i = 0; i <= 4; i++) {
  console.log(i);
}

// Wypisze:
// 0
// 1
// 2
// 3
// 4

Warto zaznaczyć, że pętla for nie jest niczym innym, jak wygodniejszą alternatywą dla pętli while, na którą zawsze może być przetłumaczona. Przyjrzyj się poniższemu porównaniu, które pomoże Ci zapamiętać sens kolejnych części w bloku for: pierwsza część stałaby przed pętlą while, druga w jej warunku, a trzecia na końcu jej ciała.

for (varDefinition; predicate; varUpdate) {
  code
}

// Jest zwykle jednoznaczne z

variableDef
while (predicate) {
  code
  varUpdate
}

Różne przypadki użycia

We współczesnym programowaniu rzadko kiedy zobaczysz w kodzie użycie pętli while. Powstały dla niej nowocześniejsze alternatywy. Nie znaczy to jednak, że nie jest istotna. Niejednokrotnie przewija się koncept powtarzania jakiejś operacji tak długo, jak długo warunek jest spełniony. Warto ją więc przećwiczyć i mieć w głowie jej znajomość.

Pętla for jest używana częściej, zazwyczaj do robienia czegoś dla kolejnych liczb lub określoną liczbę razy. Mamy kilka typowych przypadków zastosowania pętli for, które warto zapamiętać.

Poznaliśmy już pierwszy: kiedy potrzebujemy wykonać operację dla kolejnych liczb całkowitych, od pierwszej aż do ostatniej. W poprzednim przykładzie zaczęliśmy od 0, a skończyliśmy na 4. Można uogólnić ten wzorzec dla dowolnych wartości, zaczynając od firstValue, a kończąc na lastValue.

for (let i = firstValue; i <= lastValue; i++) {
  // code
}

// Przykład

for (let i = 0; i <= 4; i++) {
  console.log(i);
}

// Wypisze:
// 0
// 1
// 2
// 3
// 4

Kolejny wariant tego wzorca ma miejsce w sytuacji, gdy chcemy zrobić coś dla wszystkich liczb, z wyjątkiem ostatniej. W takim przypadku powinniśmy użyć < zamiast <=. Zauważ, że na poniższym przykładzie nie została wypisana cyfra 4.

for (let i = firstValue; i < oneAfterLastValue; i++) {
  // code
}

// Przykład

for (let i = 0; i < 4; i++) {
  console.log(i);
}

// Wypisze:
// 0
// 1
// 2
// 3

Inna spotykana modyfikacja zaistnieje wówczas, gdy nie interesują nas kolejne liczby, a na przykład co druga lub co trzecia. W takim przypadku zamiast i++, powinniśmy użyć i += 2 czy i += 3.

for (let i = firstValue; i <= lastValue; i += step) {
  // code
}

// Przykład

for (let i = 0; i <= 10; i += 3) {
  console.log(i);
}

// Wypisze:
// 0
// 3
// 6
// 9

Ostatni istotny wariant to taki, w którym chcemy odwrócić kolejność liczb i zacząć od największej, a kończyć na mniejszych. W takim przypadku powinniśmy zacząć od największej liczby, w warunku użyć najmniejszej, a przy modyfikacji zmniejszać zmienną (na przykład przy użyciu operatora dekrementacji --).

for (let i = firstValue; i >= lastValue; i--) {
  // code
}

// Przykład

for (let i = 3; i >= 0; i--) {
  console.log(i);
}

// Wypisze:
// 3
// 2
// 1
// 0

Zauważ, że wszystkie te modyfikacje mogą się łączyć na różne sposoby. Dla przykładu możemy zacząć od 10, zmniejszać o 2 w każdym kroku aż do 4, a na koniec pominąć ostatnią cyfrę.

for (let i = 10; i > 4; i = i - 2) {
  console.log(i);
}

// Wypisze:
// 10
// 8
// 6

Pętle w JavaScript wspierają także instrukcje break (powoduje natychmiastowe wyjście z pętli) oraz continue (ominięcie reszty ciała). Początkowo, naśladując większość książek, opisałem je, ale zdecydowałem się usunąć tę sekcję. Zrobiłem tak, ponieważ sam nie użyłem oraz nie widziałem ich w żadnym produkcyjnym kodzie co najmniej od roku. Aby mieć pewność, przeszukałem kilka dużych projektów JavaScript w firmie słynącej z dobrego kodu i również nie natknąłem się na ani jeden przykład ich zastosowania. Ponieważ celem tej książki jest skoncentrowanie się na aspekcie praktycznym, a nie samej teorii, usunąłem więc tą część.

Ćwiczenie: Przypadki użycia pętli for dla liczb

Z wykorzystaniem pętli for:

  • Wypisz liczby od 10 do 5.
  • Wypisz liczby od 5 do 30, z pominięciem ostatniej.
  • Wypisz co drugą liczbę od 20 do 0.

Odpowiedzi na końcu książki.

Zakończenie

Pomimo że pętle same w sobie nie są współcześnie tak często używane, to jednak są bardzo istotną częścią programowania i ich rozumienie leży u podstaw myślenia programistycznego. To był bardzo ważny rozdział. Poznanie i zrozumienie pętli będzie procentowało.

Tymczasem przejdziemy do funkcjonalności, która zdominowała współczesne projekty programistyczne, a przez wielu ekspertów określana jest jako najistotniejsza funkcjonalność języków programowania. Czas pomówić o funkcjach.

1:

Cóż, ponoć programiści słyną ze swojego lenistwa.