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 funkcje. Istnieją jednak o wiele przyjemniejsze sposoby na wykonywanie różnych operacji po tablicach.

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

Funkcja forEach()

Pierwszą z omawianych funkcji 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"];

//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);

new Array(100).forEach(() => {
    console.log("Ala ma kota"); //wykona się 100 razy, bez wypisywania żadnego elementu, indeksu itp
});

Poza pierwszym parametrem funkcja 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);

Funkcje every() i some()

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

Obie funkcje w rezultacie zwracają true lub false.

Funkcja 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

Funkcja 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

Funkcja map()

Funkcja 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, Monika, Magda]
console.log(tab2); //[MARCIN, MONIKA, MAGDA]

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]

Funkcja filter()

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", "Agnieszka", "Magda", "Monika", "Piotrek"];

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

console.log(woman); //["Agnieszka", "Magda", "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}]

Funkcja reduce()

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}];

//dlaczego poniżej muszę zwracać obiekt z kluczem age?
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ę.

Funkcja find()

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ą funkcje find(cb) oraz findIndex(cb).

Funkcja 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

Jeżeli chcemy szukać od końca, skorzystamy z funkcji findLast():


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 lastWoman = tab.find(el => el.gender === "w");
console.log(lastWoman); //{ name : "Ania", age: 20, gender: "w" }

Funkcja findIndex()

Funkcja 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...}]

Jeżeli chcemy szukać od końca, skorzystamy z metody findLastIndex():


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

const lastWomanIndex = tab.findIndex(el => el.gender === "w");
console.log(lastWomanIndex); //3

Łańcuchowe wywoływanie

Method chaining to sposób odpalania kolejnych 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 funkcje, możemy tą funkcję od razu odpalić po kropce:


const text = "Ala ma kota";

text.toUpperCase().substr(0, 3).length //kolejne funkcje 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 funkcje.


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)

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę, zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania

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.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.