Znamy już, dość pobieżnie, typ danych, jakim jest tablica. W tym epizodzie skupimy się na metodach map, filter, reduce oraz forEach, które pozwalają nam na tablicach operować. Wykonamy też ćwiczenia utrwalające.
Tablice to ważny element języka JavaScript oraz w zasadzie każdego języka programowania. Poznaliśmy już sposób ich tworzenia oraz używania ich elementów przy pomocy indeksów.
Tematem tablic zajmować się będziemy jeszcze wielokrotnie. Poznamy sposoby na ich modyfikowanie, dodawanie i usuwanie wartości i tym podobne.
Na razie skupimy się na czterech ważnych metodach tablic, jakimi są forEach, map, filter oraz reduce.
Tradycyjnie podaję templatkę pliku HTML, w którym będziemy pisać nasz kod, następnie sprawdzać jego działanie otwierając ten plik za pomocą przeglądarki
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Welcome to my website</h1>
<script>
//nasz kod tutaj
</script>
</body>
</html>
forEach – pętla przechodząca po każdym elemencie tablicy
forEach to metoda, którą możemy zastosować na tablicy. Przyjmuje ona funkcję jako argument, która otrzymuje element i wykonuje pewną akcję w pętli na każdym elemencie tablicy.
Zobaczmy sobie na przykład pętli for, która wykorzystuje indeks, aby wykonać jakąś akcję przechodząc po elementach tablicy:
<script>
names = ["Jane", "Jim", "Bob"];
for(let idx = 0; idx < names.length; idx++){
console.log(`My name is ${names[idx]}`);
}
//My name is Jane
//My name is Jim
//My name is Bob
</script>
Teraz pokażę jak dokonać podobnej akcji za pomocą pętli a w zasadzie metody forEach. Jeszcze raz:
- forEach jest metodą, wywoływaną na tablicy
- forEach przyjmuje funkcję, która ma być wykonana na każdym elemencie tablicy
- funkcja ta przyjmuje element jako argument
Teoretycznie wiedząc to moglibyśmy pewnie już sami wpaść na to, jak ta pętla może wyglądać:
<script>
names = ["Jane", "Jim", "Bob"];
names.forEach(function(name){
console.log(`My name is ${name}`);
});
//My name is Jane
//My name is Jim
//My name is Bob
</script>
Moim zdaniem forEach jest bardziej czytelna i elegancka niż tradycyjna pętla for, aczkolwiek nikt nie zabrania nam używać klasycznej pętli for, jeżeli takie nasze upodobanie.
Jak wspomniałem, forEach jest metodą obiektu typu tablica, te metody są dostępne „po kropce” i nazwie danej metody.
Metoda forEach przyjmuje funkcję (tutaj anonimową, bez nazwy), która przyjmuje element (tutaj nazwany name). Możemy, jeżeli chcemy, użyć funkcji strzałkowej:
<script>
names = ["Jane", "Jim", "Bob"];
names.forEach((name) => {
console.log(`My name is ${name}`);
});
//My name is Jane
//My name is Jim
//My name is Bob
</script>
Możemy także skorzystać z funkcji nazwanej i gdzieś zdefiniowanej:
<script>
function introduce_yourself(name) {
console.log(`My name is ${name}`);
}
names = ["Jane", "Jim", "Bob"];
names.forEach(introduce_yourself);
//My name is Jane
//My name is Jim
//My name is Bob
</script>
Metoda map – tablica zmodyfikowana
Mamy taką oto tablicę liczb:
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
</script>
Załóżmy, że chcemy utworzyć kolejną tablicę, która zawiera liczby numbers podniesione do kwadratu. I nie chcemy jej stworzyć „z palca” poprzez literał tablicy, ale dynamicznie, poprzez kod.
Możemy spróbować, mając na uwadze, że:
- map jest metodą tablicy, która zwraca nową tablicę
- map przyjmuje funkcję, która będzie wywołana na każdym elemencie tablicy
- funkcja przyjmuje element jako argument i zwraca operację na tym elemencie
- wynik map możemy przypisać do zmiennej
Możemy spróbować, albo od razu zobaczyć jak działa poniższy kod:
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
let squared = numbers.map(function(num){
return num * num;
});
console.log(squared);
//[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
</script>
Podam teraz najbardziej uproszczony zapis funkcji strzałkowej, dla większej czytelności:
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
let squared = numbers.map((num) => num * num);
console.log(squared);
//[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
</script>
Taki zapis (bez klamr) działa tylko w przypadku prostych, jednolinijkowych operacji i nawet słówko kluczowe return jest tam zbędne.
Myślę, że to najbardziej czytelny przykład tego, czym jest map.
Metoda filter – odfiltruj niepotrzebne dane
Zanim przejdziemy do filtrowania, nie zagłębiając się w zbędną matematykę, podam funkcję, która sprawdza, czy dana liczba jest parzysta:
<script>
function is_even(num){
return num % 2 === 0;
}
let numbers = [1,2,3,4,5,6,7,8,9,10];
</script>
W uproszczeniu – chodzi o to, aby liczba zwracała resztę z dzielenia przez 2 równą 0. Operator „%” to operator otrzymywania reszty z dzielenia.
Teraz dla ćwiczenia zróbmy sobie przejście po tych liczbach w pętli forEach i wypiszmy czy dana liczba jest parzysta.
Mogliśmy to zrobić na wiele sposobów, najbardziej oczywisty to:
<script>
function is_even(num){
return num % 2 === 0;
}
let numbers = [1,2,3,4,5,6,7,8,9,10];
numbers.forEach((num) => {
console.log(is_even(num));
});
//false
//true
//false
//true
//false
//true
//false
//true
//false
//true
</script>
Możemy się oczywiście pobawić odrobinkę, poznamy przy tym operator warunkowy.
<script>
function is_even(num){
return num % 2 === 0;
}
let numbers = [1,2,3,4,5,6,7,8,9,10];
numbers.forEach(function(number){
msg = `Number ${number} is `;
msg+= is_even(number) ? 'even' : 'odd';
console.log(msg);
});
//Number 1 is odd
//Number 2 is even
//Number 3 is odd
//Number 4 is even
//Number 5 is odd
//Number 6 is even
//Number 7 is odd
//Number 8 is even
//Number 9 is odd
//Number 10 is even
</script>
Mamy tutaj zmienną przejście w pętli forEach po numerach, zmienną msg, którą utworzyłem błędnie bez używania słówka kluczowego let (tak można, choć nie jest to zalecane i powoduje nieoczekiwane działania).
Do msg przypisujemy „Liczba ${liczba} jest ” oraz dodajemy albo „parzysta” albo „nieparzysta”, w zależności, czy funkcja is_even zwróci nam prawdę czy nie, to jest właśnie operator warunkowy „?” posiadający opcję dla prawdy oraz opcję dla fałszu.
Znak += to zaś dodanie tekstu do zmiennej. Kiedyś przez te „głupotki” musieliśmy przejść, to był dobry moment.
Teraz tak – postarajmy się stworzyć nową tablicę, opartą o tablicę numbers oraz funkcję is_even.
Jak się domyślamy – nowa tablica ma zawierać tylko parzyste liczby i będzie korzystała z metody filter. Metoda filter:
- jest metodą obiektu typu tablica, zwraca nową tablicę
- przyjmuje funkcję, która będzie wywołana na każdym elemencie
- funkcja przyjmuje element i ma zwracać wartość prawda/fałsz albo warunek, który do takiej wartości można sprowadzić
- do nowej zmiennej przypisana zostanie tablica zawierająca tylko te elementy, które dawały wartość „prawda”
Jeżeli czujemy się na siłach, możemy spróbować napisać to samemu. Jeżeli nie – oto kod:
<script>
function is_even(num){
return num % 2 === 0;
}
let numbers = [1,2,3,4,5,6,7,8,9,10];
let even_numbers = numbers.filter(is_even);
console.log(even_numbers);
// [2, 4, 6, 8, 10]
</script>
Mamy tutaj funkcję is_even, która przyjmuje element num i zwraca pewien warunek (reszta z dzielenia num przez 2 ma być równa zeru). Ten warunek będzie albo prawdziwy, albo fałszywy.
Mamy też filter z naszą funkcją, w efekcie czego dostajemy nową tablicę, do której odfiltrowane zostały tylko elementy parzyste.
Można to jeszcze tak zapisać:
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
let even_numbers = numbers.filter((num) => num % 2 === 0);
console.log(even_numbers);
// [2, 4, 6, 8, 10]
</script>
Metoda reduce – redukcja tablicy do typu prostego
Metoda reduce jest zdecydowanie najtrudniejsza do zrozumienia ze wszystkich metod dotąd poznanych. Jest to metoda, która redukuje całą tablicę do jednego prostego typu.
Metoda reduce posiada akumulator, czyli wartość startową, oraz funkcję, która przyjmuje dwa parametry – aktualną wartość akumulatora oraz element, po którym przechodzimy. I zwraca nowy akumulator.
Trudno to zrozumieć w samej teorii, więc zróbmy tak. Mamy tablicę liczb:
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
</script>
Chcemy zaś uzyskać sumę tych liczb. Będziemy zatem przechodzić po każdej liczbie po kolei i do akumulatora (ustawionego na początek na 0) dodawać daną liczbę. Zróbmy to w pętli for (klasycznej):
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
let acc = 0;
for(let idx=0; idx < numbers.length; idx++)
{
acc += numbers[idx];
}
console.log(acc);
//55
</script>
Akumulator ustawiony początkowo na 0, przejście po każdej liczbie z tablicy numbers, dodanie do akumulatora tej liczby, zwrócenie akumulatora. Tak mniej więcej będzie wyglądać nasze reduce:
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
let sum = numbers.reduce(function(acc, num){
return acc + num;
}, 0);
console.log(sum);
//55
</script>
Redukcja tablicy numbers, funkcja przyjmująca akumulator i element, dodająca element do akumulatora, jako drugi argument reduce – wartość początkowa akumulatora.
Mamy zatem 55, wynik dodawania tych liczb.
Dla przećwiczenie zróbmy sobie jeszcze wymnożenie tych liczb przez siebie:
<script>
let numbers = [1,2,3,4,5,6,7,8,9,10];
let product = numbers.reduce(function(acc, num){
return acc * num;
}, 1);
console.log(product);
//3628800
</script>
Podchwytliwe o tyle, że jako wartość początkową akumulatora musimy podać 1 (mnożenie przez 0 da nam zero, zaś gdyby nasza tablica zawierała choć 1 element, to ten element zostałby przez 1 pomnożony i zwrócony).
Zwraca nam 1 * 1 * 2 * 3 * 4 …. * 10, czyli 3628800
Zadanie 1 – do wielkiej litery
Mamy tablicę names, zawierającą imiona:
<script>
let names = ["Jim", "John", "Bob", "Jane"];
</script>
Chcemy za pomocą map otrzymać kopię tej tablicy, z imionami zapisanymi wielką literą.
Do dzieła:
<script>
let names = ["Jim", "John", "Bob", "Jane"];
let names_upper = names.map((name) => name.toUpperCase());
console.log(names_upper);
//['JIM', 'JOHN', 'BOB', 'JANE']
</script>
Zadanie 2 – tylko na literę J
Mamy tablicę names, zawierającą imiona:
<script>
let names = ["Jim", "John", "Bob", "Jane"];
</script>
Chcemy stworzyć kopię tej tablicy, która zawiera tylko te imiona, które zaczynają się na literę „J”.
Użyjemy do tego metody filter.
<script>
let names = ["Jim", "John", "Bob", "Jane"];
let startswith_j = names.filter(function(name){
return name[0] === 'J';
})
console.log(startswith_j);
//['Jim', 'John', 'Jane']
</script>
Metoda filter na tablicy names, funkcja, która przyjmuje imię i zwraca porównanie, czy indeks zerowy tego imienia równa się „J”.
Przechodzą tylko te imiona, które zaczynają się na wybraną przez nas literę.
Zadanie 3 – suma długości imion
Ta sama tablica names:
<script>
let names = ["Jim", "John", "Bob", "Jane"];
</script>
Chcemy dodać do siebie długość każdego imienia. Mamy otrzymać wartość, którą są długości (w literach) każdego imienia dodane do siebie.
Użyjemy funkcji reduce:
<script>
let names = ["Jim", "John", "Bob", "Jane"];
let sum = names.reduce(function(acc, name){
return acc + name.length;
}, 0);
console.log(sum);
//14
</script>
Metoda reduce, funkcja przyjmująca akumulator oraz imię. Do akumulatora dodajemy długość imienia. Akumulator ustawiony początkowo na 0.
Oczywiście wszystkie te zadania można wykonać za pomocą pętli for. Mimo wszystko – poznaliśmy dzisiaj kawałek wcale nie takiego prostego kodu JavaScript.
Myślę, że to dobry moment, aby zakończyć. Temat JavaScript, w tym tablic, będziemy kontynuować i jeszcze wiele kodu razem napiszemy.
Do następnego razu.