Tablice - tematy dodatkowe

Dowiedzieliśmy się już, że możemy przemieszczać się po tablicy korzystając z klasycznej pętli for:


const tab = ["Marcin", "Monika", "Magda", "Piotrek", "Grześ", "Magda"];

for (let i=0; i<tab.length; i++) {
    console.log(tab[i]);
}

Można powiedzieć, że pętla for jest typowo "manualna". Trzeba ustawić licznik, kiedy ma się zakończyć i jak ma być zwiększany. Dodatkowo ręcznie trzeba się odwoływać do danego elementu w tablicy.

Nie ma nic złego w korzystaniu z tej metody. Istnieją jednak o wiele przyjemniejsze sposoby na wykonywanie różnych operacji po tablicach.

Poniżej przyjrzymy się kilku dodatkowym tablicowym metodom, dzięki którym codzienne czynności na tablicach stają się łatwiejsze do wykonania.

forEach() - automatyczna pętla po tablicy

Pierwszą z omawianych metod jest forEach(). Przyjmuje ona jako parametr funkcję, w której możemy ustawić 3 parametry:

  • pierwszy parametr będzie wskazywał na dany element z tablicy,
  • drugi parametr będzie wskazywał na indeks elementu
  • trzeci będzie wskazywał na aktualną tablicę po której iterujemy

const tab = ["Marcin", "Monika", "Magda"];

//pod zmienną el trafią kolejne elementy
tab.forEach(el => {
    console.log(el.toUpperCase());
});

const tab = ["Marcin", "Monika", "Magda"];

//tutaj nie wykorzystuje żadnej zmiennej więc nie muszę używać parametrów
tab.forEach(() => {
    //wykonam się tyle razy co liczba elementów w tablicy
    console.log("Lubię placki");
});

const tab = ["Marcin", "Monika", "Magda"];

//pod zmienną i będzie wstawiany indeks elementu
tab.forEach((el, i) => {
    console.log(`Aktualny element to ${el}, a jego indeks to ${i}`);
});

const tab = ["Marcin", "Ania", , "Agnieszka"];

//pod zmienną arr wstawiana będzie aktualna tablica po której iterujemy - może kiedyś to się przyda?
tab.forEach((el, i, arr) => {
    console.log(`Indeks elementu to ${i}, a długość tablicy to ${arr.length}`);
});

const tab = ["Marcin", "Monika", "Magda"];

function printDetails(el, i, arr) {
    console.log(el, i, arr);
}

tab.forEach(printDetails);

Poza pierwszym parametrem - funkcją - metoda forEach może przyjąć jeszcze drugi parametr, który służy do ustawiania this w jej wnętrzu. Gdy jako parametru używamy klasycznej funkcji, domyślnie wewnątrz forEach (ale także i map, every, some, filter itp) zmienna this wskazuje na obiekt window:


const tab = ["Marcin", "Monika", "Magda"];

const ob = {
    name : "Marcin"
};

tab.forEach(function() {
    console.log(this); //window
});

tab.forEach(function() {
    console.log(this); //ob
}, ob);

Osobiście nie przejmował bym się tym parametrem, ponieważ w większości przypadków i tak będziesz używał forEach z funkcją strzałkową:


const ob = {
    tab : ["a", "b", "c"],

    print() {

        this.tab.forEach(function(el) {
            console.log(this); //window
        })

        this.tab.forEach(function(el) {
            console.log(this); //ob
        }, ob)

        this.tab.forEach(el => {
            console.log(this); //ob
        })
    }
}

ob.print();

every() i some() - dla wszystkich, dla minimum jednego

Metody every() i some() służą do sprawdzania czy wszystkie lub czy chociaż jeden element w tablicy spełnia dany warunek.

Obie metody w rezultacie zwracają true lub false.

Metoda every() zwróci prawdę, kiedy przekazana w parametrze funkcja zwróci prawdę dla każdego elementu w tablicy.


const tab = [1, 3, 5, 8, 9];

//sprawdzam czy wszystkie liczby są parzyste
const allEven = tab.every(el => el % 2 === 0);

console.log(allEven); //false

const tab = [
    { name : "Piotr", age: 18 },
    { name : "Ania", age: 15 },
    { name : "Monika", age: 16 }
];

//czy wszyscy użytkownicy są pełnoletni?
const allAdult = tab.every(el => el.age >= 18);

console.log(allAdult); //false

Metoda some() zwróci prawdę, jeżeli chociaż dla jednego elementu użyta funkcja zwróci prawdę.


const tab = ["kot", "pies", "świnka", "jeż"];

//sprawdzam czy chociaż jedno słowo ma minimum 3 litery
const word3letter = tab.some(el => el.length >= 3);

console.log(word3letter); //true

const tab = [
    { name : "Piotr", age: 18 },
    { name : "Ania", age: 15 },
    { name : "Monika", age: 16 }
];

//a może chociaż jeden user jest pełnoletni?
console.log( tab.some(el => el.age >= 18) ); //true

map() - zwracanie nowej tablicy

Metoda map() robi pętlę po tablicy i każdorazowo zwraca nowy element tablicy. W wyniku po zakończeniu całej pętli zwracana jest nowa tablica z taką samą liczbą elementów:


const tab = ["Marcin", "Monika", "Magda"];

const tab2 = tab.map(el => el.toUpperCase());

console.log(tab); //[Marcin, Ania, Agnieszka]
console.log(tab2); //[MARCIN, ANIA, AGNIESZKA]

const tab = [1, 2, 3];
const tab2 = tab.map(el => el * 2);

console.log(tab2); //[2, 4, 6]

const numbers = [1.2, 4.5, 9.3];

const absolute = numbers.map(el => Math.ceil(el));
console.log(absolute); //[2, 5, 10]

function multiple3(number) {
    return number * 3;
}

var ourTable = [1, 2, 3];
console.log(ourTable.map(multiple3)); //[3, 6, 9]

filter() - filtrowanie elementów

Bardzo często będziemy chcieli przefiltrować daną tablicę zwracając tylko elementy, które pasują do danego warunku.

Funkcja filter() zwraca nową tablicę, która zawiera odfiltrowane elementy:


const tab = [1, 2, 3, 4, 5, 6];

const evenNumbers = tab.filter(el => el % 2 === 0);

console.log(evenNumbers); //[2, 4, 6]

const tab = ["Marcin", "Monika", "Magda", "Monika", "Piotrek"];

const woman = tab.filter(name => name.endsWith("a"));

console.log(woman); //["Ania", "Agnieszka", "Monika"]

const tab = [
    { name : "Piotr", age: 18 },
    { name : "Ania", age: 15 },
    { name : "Monika", age: 16 },
    { name : "Andrzej", age: 20 },
];

const adultUsers = tab.filter(user => user.age >= 18);

console.log(adultUsers); //[{ name : "Piotr", age: 18 },  { name : "Andrzej", age: 20}]

reduce() - redukowanie tablicy

Dzięki reduce() możemy wykonywać operacje na tablicy "redukując ją", w wyniku uzyskując jakiś wynik.

Funkcja robi iterację po tablicy. W pierwszej iteracji pod pierwszy parametr wstawiany jest pierwszy element tablicy, a pod drugi kolejny. Funkcja zwraca jakiś wynik. W kolejnej iteracji podstawiany jest on pod pierwszy parametr, a do drugiego parametru trafia kolejny element w tablicy.


const tab = [1, 2, 3, 4];

const result = tab.reduce((prev, curr) => prev + curr);

//1 iteracja => prev = 1, curr = 2
//2 iteracja => prev = 3, curr = 3
//3 iteracja => prev = 6, curr = 4
//wynik = 10

const tab = [3, 2, 4, 2];

const result = tab.reduce((a, b) => a * b);

//1 iteracja => prev = 3,  curr = 2
//2 iteracja => prev = 6,  curr = 4
//3 iteracja => prev = 24, curr = 2

//wynik = 48

Kolejnym parametrem poza funkcją jest wartość początkowa:

Gdybyś sumował tablicę za pomocą zwykłej pętli, musiałbyś stworzyć dodatkowa zmienną:


const tab = [3, 2, 4, 2];
let sum = 0;

for (let i=0; i<tab.length; i++) {
    sum += tab[i];
}

Omawiany parametr to właśnie ta początkowa wartość.


//atrybut po funkcji to początkowa wartość
const sum = [1, 2, 3].reduce((a, b) => a + b, 0);

console.log(sum); //6

const sum = [1, 2, 3].reduce((a, b) => a + b, "");

console.log(sum); //"123"

const data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

const flatArray = data.reduce((total, amount) => total.concat(amount), []);

console.log(flatArray); //[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

const data = [{age : 10}, {age : 12}, {age : 15}];

const age = data.reduce((a, b) => a + b.age, 0);
//lub
const age = data.reduce((a, b) => {age : a.age + b.age}, {age : 0});

console.log(age); //37

Do reduce() trzeba się przyzwyczaić. Osobiście stosuję ją do kilku prostych sytuacji typu sumowanie, mnożenie itp. Ale gdy pojawia się coś bardziej skomplikowanego, sięgam po for of. Nawyk, wygoda? Ciężko powiedzieć. Zresztą nie tylko ja tak robię.

find() - zwracanie pierwszego pasującego

Metody indexOf(), lastIndexOf() i includes() są używane głównie do wyszukiwania typów prostych - liczb i tekstów.

Możemy za ich pomocą szukać także obiektów, musimy mieć jednak do nich referencję:


const ob = { name : "Jan" }
const things = ["ala", "bala", "cala", ob, "data"];

console.log(things.indexOf(ob)); //3
console.log(things.includes(ob)); //true

Dość często będzie zdarzać się sytuacja, gdzie będziemy chcieli wyszukać jakiś obiekt po jego właściwościach - np. po właściwości name, age czy każdej innej. W takim przypadku z pomocą przyjdą metody find(cb) oraz findIndex(cb).

Metoda find(cb) zwraca pierwszy pasujący element, który spełnia dany warunek (przekazana funkcja zwrotna zwróci prawdę):


const tab = [
    { name : "Karol", age: 10, gender: "m" },
    { name : "Beata", age: 13, gender: "w" },
    { name : "Marcin", age: 18, gender: "m" },
    { name : "Ania", age: 20,gender: "w" }
];

const firstWoman = tab.find(el => el.gender === "w");
console.log(firstWoman); //{ name : "Beata", age: 13, gender: "w" }

const adult = tab.find(el => el.age >= 18);
console.log(adult) //{ name : "Marcin", age: 18, gender: "m" }

const tab = [12, 5, 8, 130, 44];

const bigNr = tab.find(el => el >= 15);
console.log(bigNr); //130

findIndex() - zwracanie indeksu pasującego elementu

Metoda findIndex() działa bardzo podobnie do powyższej, z tym, że zwraca indeks pierwszego pasującego elementu:


const tab = [
    { name : "Karol", age: 10, gender: "m" },
    { name : "Beata", age: 13, gender: "w" },
    { name : "Marcin", age: 18, gender: "m" },
    { name : "Ania", age: 20,gender: "w" }
];

const index = tab.findIndex(el => el.gender === "m")

console.log("index pasującego elementu:", index);
console.log("Pasujący element:", tab[index]);

const tab = [
    { name : "Monika", gender: "w" },
    { name : "Agata", gender: "w" },
    { name : "Marcin", gender: "m" },
    { name : "Patrycja", gender: "w" }
]

const index = tab.findIndex(el => el.gender === "m")
tab.splice(index, 1); //usuwam wyszukany element
console.log(tab); //[{Monika...}, {Agata...}, {Patrycja...}]

Łańcuchowe wywoływanie metod

Method chaining to sposób odpalania kolejnych metod (funkcji), które zapisujemy po kropce. Technika ta tyczy się nie tylko tablic, ale całego JavaScript.

Odpalenie funkcji powoduje zwrócenie jakiś wartości. Jeżeli taka zwrócona wartość pasuje dla kolejnej metody, możemy tą metodę od razu odpalić po kropce:


const text = "Ala ma kota";

text.toUpperCase().substr(0, 3).length //kolejne metody odpalamy po kropce

//czasami trzymanie wszystkiego w jednej linii nie jest dobrym rozwiązaniem
text
    .toUpperCase()
    .substr(0, 3)
    .length

Podobnie jest z powyższymi funkcjami dotyczącymi tablic. Jeżeli dana funkcja zwraca tablicę (np. map() czy filter() zwracają tablicę) możemy od razu po kropce odpalać kolejne metody.


const tab = ["Marcin", "Monika", "Magda"];

const newTab = tab
    .map(el => el.toLowerCase()) //zwracam nową tablicę...
    .filter(el => el.endsWith("a")) //...więc mogę ją odfiltrować
    .map(el => el + "!") //...filter zwróciło tablicę więc mogę użyć map
    .forEach(el => console.log(el)) //...map zwróciło tablicę więc forEach pasuje

console.log(newTab)

Powyższy przykład można jeszcze bardziej uprościć stosując zamiast funkcji anonimowych referencje do funkcji. Dzięki temu nie tylko nasz zapis się upraszcza, ale i zyskujemy dodatkowe funkcje dla przyszłego wykorzystania.


const tab = ["Marcin", "Monika", "Magda"];

const lower = function(el) {
    return el.toLowerCase();
}

const checkLastLetterA = function(el) {
    return el.endsWith("a");
}

const addExclamationMark = function(el) {
    return el + "!"
}

const showElement = function(el) {
    console.log(el);
}

const newTab = tab
    .map(lower)
    .filter(checkLastLetterA)
    .map(addExclamationMark)
    .forEach(showElement);

console.log(newTab)

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.