Lekcja 3 Tablice w JavaScript

W języku JavaScript tablice (ang. arrays) służą do przechowywania uporządkowanych list wartości:
liczb, łańcuchów, obiektów, a nawet innych tablic. Są dynamiczne, mają wbudowane metody i stanowią fundament pracy z danymi.

Tworzenie tablic i podstawy składni

Najczęściej tablice tworzymy za pomocą nawiasów kwadratowych []:

const puste = [];
const liczby = [1, 2, 3];
const miks = [1, 'Ala', { imie: 'Jan' }, [10, 20]];

Dostęp do elementów i długość

Elementy indeksujemy od zera. Właściwość length zwraca aktualną liczbę elementów:

const kolory = ['czerwony', 'zielony', 'niebieski'];
kolory[0];         // 'czerwony'
kolory[kolory.length - 1]; // ostatni element
kolory.length;    // 3

Dodawanie i usuwanie elementów

  • push(...) – dodaje na koniec; pop() – usuwa z końca.
  • unshift(...) – dodaje na początek; shift() – usuwa z początku.
  • splice(index, ile, ...nowe) – wstawia/usuwa w środku (modyfikuje tablicę).
  • slice(start, end) – kopiuje fragment (nie modyfikuje oryginału).
const a = [1,2];
a.push(3);     // [1,2,3]
a.unshift(0);  // [0,1,2,3]
a.pop();         // [0,1,2]
a.splice(1, 1, 9); // [0,9,2]
const b = a.slice(0, 2); // [0,9]

Iteracja po tablicach

Do przechodzenia po elementach możesz użyć pętli for, for...of lub metod iteracyjnych:

const liczby = [1,2,3];
// klasyczny for for (let i = 0; i < liczby.length; i++) { console.log(liczby[i]);
}
// for...of for (const n of liczby) { console.log(n);
}
// forEach
liczby.forEach((n, i) => console.log(i, n)); 

Mapowanie, filtrowanie i redukcja

Funkcyjne metody zwracają nowe tablice lub wartość będącą wynikiem agregacji:

const ceny = [10, 20, 30];
const zVat = ceny.map(c => c * 1.23);     
// [12.3, 24.6, 36.9] 
const drogie = zVat.filter(c => c > 20);   // [24.6, 36.9] 
const suma = zVat.reduce((acc, c) => acc + c, 0); // 73.8 

Wyszukiwanie i sprawdzanie

  • includes(x) – czy tablica zawiera element x.
  • indexOf(x) / lastIndexOf(x) – indeks (lub -1, gdy brak).
  • find(fn) – zwraca pierwszy pasujący element.
  • some(fn) / every(fn) – sprawdza warunek dla części/całości.
const users = [
  { id: 1, name: 'Ala' },
  { id: 2, name: 'Ola' }
];
users.find(u => u.id === 2);     // {id:2, name:'Ola'}
users.some(u => u.name.startsWith('A')); // true
users.every(u => u.id > 0);             // true 

Sortowanie i porządkowanie

sort() modyfikuje tablicę w miejscu. Dla liczb i polskich znaków dodaj funkcję porównującą:

const liczby = [10, 2, 33];
liczby.sort((a, b) => a - b); // [2,10,33]
const miasta = ['Żory', 'Warszawa', 'Łódź'];
miasta.sort((a, b) => a.localeCompare(b, 'pl')); 

Operator spread, rest i destrukturyzacja

// kopiowanie i łączenie (płytkie)
const a = [1,2];
const b = [...a, 3];      // [1,2,3]
const c = [...a, ...b];     // [1,2,1,2,3]
// rest w parametrach function suma(...nums) { return nums.reduce((acc, n) => acc + n, 0);
}
// destrukturyzacja const [pierwszy, drugi, ...reszta] = c; 

Spłaszczanie, tworzenie i tablice “podobne”

  • flat(n) – spłaszcza zagnieżdżone tablice do poziomu n.
  • flatMap(fn) – łączy map() i flat(1).
  • Array.from(iterowalne) – tworzy tablicę z iterowalnych/“array-like”.
  • Array.of(...) – tworzy tablicę z przekazanych wartości.
[1, [2, [3]]].flat(2); // [1,2,3]
Array.from('ABC');      // ['A','B','C']
Array.of(7);            // [7]

Tablice wielowymiarowe i przykładowe użycie

const macierz = [
  [1, 2, 3],
  [4, 5, 6]
];
// suma wszystkich komórek const total = macierz
.flat()
.reduce((acc, n) => acc + n, 0); 

Kopiowanie: płytkie vs głębokie

Operacje płytkie (np. [...arr], slice()) kopiują tylko pierwszy poziom – zagnieżdżone obiekty/tabl.

const oryg = [{ x: 1 }];
const kopiaPlytka = [...oryg];
kopiaPlytka[0].x = 99;
oryg[0].x; // 99 - ten sam obiekt w środku

Do głębokiej kopii (proste przypadki) można użyć np. structuredClone(ary) lub JSON.parse(JSON.stringify(ary)) (z ograniczeniami).

Częste pułapki i dobre praktyki

  • Nie używaj delete arr[i] – tworzy “dziurę” i nie przesuwa indeksów. Zamiast tego użyj splice().
  • Pamiętaj, że sort() i wiele metod modyfikuje tablicę. Jeśli potrzebujesz niezmienności – pracuj na kopii ([...arr]).
  • length można przyciąć: arr.length = 0 opróżni tablicę; ale rób to świadomie.
  • Równość tablic porównuje referencje: [1,2] === [1,2] to false. Porównuj elementy, nie referencje.
  • for...in nie jest do tablic (iteruje po kluczach/łańcuchach prototypu). Używaj for, for...of, forEach.
  • Pracując z “array-like” (np. NodeList), użyj Array.from() do konwersji na prawdziwą tablicę.
  • Dla dużych kolekcji rozważ metody nie-alokujące niepotrzebnych kopii i unikaj zagnieżdżonych pętli O(n²) gdy to możliwe.

Przykład: pipeline na danych

// Zadanie: z listy produktów wybierz aktywne, posortuj po cenie rosnąco
const produkty = [
  { id: 1, nazwa: 'Kompresor', cena: 399, aktywny: true },
  { id: 2, nazwa: 'Membrana', cena: 59,  aktywny: false },
  { id: 3, nazwa: 'Zestaw pneumatyczny', cena: 249, aktywny: true }
];
const wynik = produkty
.filter(p => p.aktywny)
.slice() // kopia, by nie modyfikować oryginału przy sortowaniu
.sort((a, b) => a.cena - b.cena)
.map(p => `${p.nazwa} – ${p.cena} zł`);
console.log(wynik); // ["Zestaw pneumatyczny – 249 zł", "Kompresor – 399 zł"] 

Tablice vs. obiekty – kiedy co wybrać?

  • Użyj tablicy, gdy liczy się kolejność, indeksy liczbowe i operacje na listach.
  • Użyj obiektu, gdy potrzebujesz par klucz–wartość z kluczami jako nazwami (np. słowniki).
  • Rozważ Map/Set, gdy ważna jest wydajność operacji wyszukiwania/unikalności.

Nowości i warte uwagi metody

  • at(index) – czytelny dostęp, wspiera indeksy ujemne: arr.at(-1).
  • toSorted(), toReversed(), toSpliced() – zwracają kopie bez modyfikacji oryginału.
  • with(index, value) – kopia z podmienioną pozycją (bez mutowania oryginału).
  • findLast(), findLastIndex() – wyszukiwanie od końca.
const arr = [1,3,2];
arr.toSorted((a,b) => a-b); // [1,2,3] – oryginał zostaje [1,3,2]
arr.with(1, 99);     // [1,99,2]
arr.at(-1);            // 2

Podsumowanie

Tablice to elastyczna i potężna struktura danych w JavaScript. Pozwalają wydajnie przechowywać i przetwarzać listy, a bogaty zestaw metod – od prostych (push, slice) po funkcyjne (map, filter, reduce) i niemutujące (toSorted, with) – ułatwia pisanie czytelnego, bezpiecznego i wydajnego kodu. Pamiętaj o pułapkach (mutacje, delete, płytkie kopie) i świadomie wybieraj narzędzia – dzięki temu praca z danymi będzie przyjemna i przewidywalna.

Zostaw komentarz

Koszyk