# Tablice

Wyobraź sobie, że musisz przechować 10 różnych wartości. Tworzysz więc 10 zmiennych.\
I wszystko jest w porządku do czasu, gdy dostajesz zadanie, by każdą taką zmienną zwiększyć np. o 2. Zaczynają się schody.\
A schody te zwiększają się gdy dochodzisz do momentu kiedy nie możesz z góry określić ile masz tych zmiennych, lub gdy tych zmiennych robi się nie 10, a na przykład 2000 razy tyle.

Tutaj przychodzą z pomocą tablice, które są uporządkowanymi zbiorami zawierającymi jakieś dane. Dość klasycznym porównaniem jest szafa z bibliotecznymi kartami książek. Półka - tablica - przechowuje karty książek - u nas zmienne.

### Tworzenie nowej tablicy <a href="#nowa_tablica" id="nowa_tablica"></a>

Aby stworzyć nową tablicę korzystamy z kwadratowych nawiasów:

```js
const tab = []; //pusta tablica

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

const tab3 = ["Marcin", "Ania", "Agnieszka"];
```

```js
const a = "ALA";
const b = 234;
const c = "PIES";

const tab = [a, b, c, "KOT", {...}]; //tablica z 5 elementami. Mogą to być oddzielne zmienne, ale też wartości wpisane bezpośrednio w tablicy
```

Drugim sposobem tworzenia tablic - o wiele rzadziej używanym jest użycie [konstruktora](https://kursjs.pl/kurs/obiekty/konstruktor):

```js
const tab = new Array(10);
console.log(tab); //[blank x 10]

const tab = new Array("Ala", "Bala", "Cala"); //gdy podamy więcej wartości staną się one elementami tablicy
console.log(tab); //["Ala", "Bala", "Cala"]
```

<figure><img src="https://kursjs.pl/kurs/super-podstawy/tablica.png" alt=""><figcaption></figcaption></figure>

Kolejne elementy tablicy ponumerowane za pomocą tak zwanych **indeksów**, które numerowane są od 0 (więcej na ten temat dowiesz się w rozdziale o obiektach).\
Pierwsza wartość w tablicy ma indeks `0`, druga `1`, trzecia **2** i tak dalej, aż do ostatniego indeksu który wynosi długość tablicy - 1 czyli `tab.length-1`.

Po stworzeniu tablicy aby pobrać dane elementy z tablicy wystarczy się do nich odwołać przez nazwę tablicy i indeks, który podajemy w kwadratowych nawiasach:

```js
const tab = ["Ala", "ma", "rudego", "kota"];

console.log( tab[0] ); //Ala
console.log( tab[1] ); //ma
console.log( tab[2] ); //rudego
console.log( tab[3] ); //kota
console.log( tab[tab.length-1] ); //ostatni element - kota
```

W najnowszej wersji Javascript (ES2022) do odwoływania się do elementów możemy też użyć funkcji `at(index)`. Jako index możemy tutaj podać liczbę dodatnią, ale też ujemną, dzięki czemu pobierzemy element od końca tablicy:

```js
const tab = ["Ala", "ma", "rudego", "kota"];
tab.at(0); //Ala
tab.at(1); //ma
tab.at(2); //rudego
tab.at(-1); //kota
tab.at(-2); //rudego
```

### Właściwość length <a href="#length" id="length"></a>

Każda tablica udostępnia nam właściwość **length**, która określa jej długość (czyli ilość jej elementów).\
Dzięki temu możemy poznać nie tylko długość tablicy, ale też indeks ostatniego elementu oraz w łatwy sposób przeprowadzać pętlę po wszystkich elementach naszej tablicy.

```js
//indeks:       0         1         2
const tab = ["Marcin", "Ania", "Agnieszka"];

console.log( tab.length ); //3
console.log( tab[ tab.length-1 ] ); //Agnieszka

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

### Dodawanie elementów do tablicy <a href="#push" id="push"></a>

Aby dodać do tablicy element na jej końcu, możemy użyć funkcji **push(element1, element2, ...)**:

```js
const tab = ["Marcin", "Ania", "Agnieszka"];
tab.push("Piotrek");
console.log(tab); //[Marcin, Ania, Agnieszka, Piotrek]

tab.push("Y", "Z");
console.log(tab); //[Marcin, Ania, Agnieszka, Piotrek, Y, Z]
```

Mniej bezpieczną metodą jest po prostu ustawianie elementu na danym indeksie:

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

tab[3] = "Piotrek";
//lub
tab[tab.length] = "Piotrek";

console.log(tab); //[Marcin, Ania, Agnieszka, Piotrek]
```

Poza powyższymi podstawami, tablice udostępniają nam dużą liczbę metod, [które możemy na nich używać](https://developer.mozilla.org/pl/docs/Web/JavaScript/Referencje/Obiekty/Array).\
Poniżej poznamy te najczęściej używane.

### Czy tablica jest tablicą? <a href="#sprawdzanie" id="sprawdzanie"></a>

Żeby sprawdzić czy dana zmienna jest tablicą, powinniśmy skorzystać z funkcji **Array.isArray()**. Wynika to z faktu, że tablice w Javascript także są obiektami i typeof zwraca nam "object":

```js
const tab = ["ala", "bala"]
const ob = { name : "Piotr" }

console.log(Array.isArray(tab)); //true
console.log(Array.isArray(ob)); //false

console.log(typeof tab); //"object";
console.log(typeof ob); //"object";
```

### Metody push() i pop() <a href="#push-pop" id="push-pop"></a>

Funkcja **push(el1, el2\*...)** wstawia jeden lub więcej elementów na końcu tablicy, po czym zwraca jej długość:

```js
const tab = ["Marcin", "Ania", "Agnieszka"];
tab.push("Grzegorz");
console.log(tab) //["Marcin", "Ania", "Agnieszka", "Grzegorz"]
tab.push("Piotr", "Karol");
console.log(tab) //["Marcin", "Ania", "Agnieszka", "Grzegorz", "Piotr", "Karol"]
```

Funkcja **pop()** w przeciwieństwie do push() zabiera ostatni element z tablicy i go zwraca:

```js
const tab = ["Marcin", "Ania", "Agnieszka"];
const last =  tab.pop();

console.log(last); //Agnieszka
console.log(tab); //[Marcin, Ania]
```

### Metody unshift() i shift() <a href="#unshift-shift" id="unshift-shift"></a>

Funkcja **unshift(el1, el2\*...)** wstawia jeden lub kilka elementów na początku tablicy, po czym zwraca nową długość tablicy.

```js
const tab = ["Marcin", "Ania", "Agnieszka"];
tab.unshift("Bartek");
console.log(tab); //[Bartek, Marcin, Ania, Agnieszka]
tab.unshift("Piotrek", "Paweł");
console.log(tab); //[Piotrek, Paweł, Bartek, Marcin, Ania, Agnieszka]
```

Funkcja **shift()** natomiast usuwa pierwszy element z tablicy i zwraca jego wartość:

```js
const tab = ["Marcin", "Ania", "Agnieszka"];
const first = tab.shift();

console.log(tab); //[Ania, Agnieszka]
console.log(first); //Marcin
```

### Funkcja join() <a href="#join" id="join"></a>

Funkcja **join(separator)** służy do łączenia kolejnych elementów tablicy w jeden wspólny tekst.\
Opcjonalny parametr separator oznacza tekst, który będzie oddzielał kolejne elementy w utworzonym tekście. Domyślnie jego wartość to `,`:

```js
const ourTable = ["Marcin", "Ania", "Agnieszka"];

console.log(ourTable.join()); //Marcin,Ania,Agnieszka

console.log(ourTable.join(" - ")); //Marcin - Ania - Agnieszka

console.log(ourTable.join(" <--> ")); //Marcin <--> Ania <--> Agnieszka
```

```js
//zliczam liczbę liter w tablicy
const cars = ["Mercedes", "Audi", "BMW"];
console.log(cars.join("").length); //15
```

### Zamiana tekstu na tablicę <a href="#spread" id="spread"></a>

Skoro powyżej zamieniliśmy tablicę na tekst, to spróbujmy zrobić to w drugą stronę. Aby to zrobić, wystarczy użyć składni spread:

```js
const txt = "kartofel";
const tab = [...txt];
console.log(tab); //["k", "a", "r", "t", "o", "f", "e", "l"]
```

Jeżeli chcielibyśmy tekst podzielić na podstawie znaku podziału, użyjemy do tego funkcji split() dostępnej dla tekstów:

```js
const txt = "Ala ma kota";
const tab = txt.split(" ");
console.log(tab); //["Ala", "ma", "kota"];
```

### Funkcja reverse() <a href="#reverse" id="reverse"></a>

Funkcja **reverse()** służy do odwracania kolejności tablicy:

```js
const tab = [1, 2, 3, 4];
tab.reverse()
console.log(tab); //[4, 3, 2, 1]
```

```js
const word = "kajak";
const tab = [...word];
console.log(tab.reverse().join("") === tab.join("")); //true czyli palindrom
```

### Metody indexOf(), lastIndexOf() i includes() <a href="#indexof" id="indexof"></a>

Wyszukać element w tablicy możemy na kilka sposobów.

Pierwszy z nich polega na skorzystaniu z funkcji **indexOf(str)** (która też jest dostępna dla [stringów](https://kursjs.pl/kurs/super-podstawy/string#indexOf)).

Funkcja zwraca indeks na którym znalazła szukany tekst/obiekt, lub **-1**, jeżeli danego elementu nie znalazła:

```js
const tab = ["Marcin", "Ania", "Agnieszka", "Monika"];

console.log(tab.indexOf("Agnieszka")); //2
console.log(tab.indexOf("Karolina")); //-1

if (tab.indexOf("Ania") !== -1) {
    console.log("Ania występuje w tablicy pod indexem", tab.indexOf("Ania"));
}
```

Kolejna metoda to lastIndexOf(), która działa bardzo podobnie, ale zwraca ostatnią pozycję szukanego tekstu:

```js
const tab = ["Agnieszka", "Marcin", "Ania", "Agnieszka", "Monika"];
console.log(tab.lastIndexOf("Agnieszka")) //3;
```

Kolejna funkcja to includes(), która zwraca prawdę lub fałsz w zależności czy szukana wartość znajduje się w tablicy:

```js
const tab = ["Marcin", "Ania", "Agnieszka", "Monika"];

if (tab.includes("Ania")) {
    console.log("Ania występuje w tablicy pod indeksem", tab.indexOf("Ania"));
}

if (!tab.includes("Pies")) {
    console.log("Pies nie występuje w tej tablicy");
}
```

Wszystkich trzech metod możemy użyć także do wyszukiwania obiektów w tablicy, co staje się przydatne, gdy chcemy znaleźć index np. klikniętego przycisku w stronicowaniu.

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

console.log(things.indexOf(ob)); //3
```

```js
//bardziej realny przykład
const buttons = [...document.querySelectorAll(".pagination-btn")];

buttons.forEach(btn => {
    btn.addEventListener("click", () => {
        const index = buttons.indexOf(btn);
        showSlide(index);
    });
});
```

### Funkcja sort() <a href="#sort" id="sort"></a>

Funkcja **sort(fn\*)** służy do sortowania tablic.

```js
const tab = ["Marcin", "Ania", "Piotrek", "Grześ"];
tab.sort();
console.log( tab ); //["Ania", "Grześ", "Marcin", "Piotrek"]
```

Standardowo JavaScript segreguje tablice leksykograficznie (słownikowo). Oznacza to, że liczby traktowane są jak słowa.\
W większości przypadków powoduje to nieoczekiwane rezultaty:

```js
const tab = [1, 2, 21, 2.1, 32, 3.1];
tab.sort();

console.log(tab); //[ 1, 2, 2.1, 21, 3.1, 32 ]
```

Ale nie tylko przy liczbach funkcja ta nie zadziała. Spójrz na poniższy przykład:

```js
const tab = ["Bartek", "ania", "Celina", "agnieszka"];
tab.sort();

console.log(tab); //["Bartek", "Celina", "agnieszka", "ania"]
```

Powyższy "błąd" wynika z faktu, że duże litery występują przed małymi.

Aby móc posegregować naszą tablicę według własnych kryteriów (przy okazji naprawiając powyższe niedoskonałości), musimy do funkcji sort przekazać własną opcjonalną funkcję.

```js
function mySort(a, b) {
    ...
}

tab.sort(mySort);
```

Javascript wykorzysta ją do porównywania kolejnych elementów w tablicy. Sama funkcja powinna zwracać odpowiednie wartości:

* Jeżeli funkcja zwróci wartość mniejszą od 0, to element **a** zostanie ustawiony przed elementem **b**
* Jeżeli funkcja zwróci wartość większą od 0, to element **a** zostanie ustawiony za elementem **b**
* Jeżeli funkcja zwróci wartość równą 0, to wartość oba elementy nie zostaną przestawione

```js
function compare(a, b) {
    if (a < b) {
        return -1
    }
    if (a > b) {
        return 1
    }
    return 0
}

tab.sort(compare);
```

Powyższe działanie obrazuje poniższy film:

Przy czym puryści Javascript mogli by się przyczepić, że przecież zastosowany w Javascript algorytm sortujący jest [bardziej skomplikowany](https://stackoverflow.com/questions/234683/javascript-array-sort-implementation).

Przykładowo aby posegregować wartości liczbowe musimy utworzyć funkcję:

```js
function compareNr(a, b) {
    return a - b
}

const tab = [100, 320, 10, 25, 310, 1200, 400];

const tab3 = tab.sort(compareNr);
console.log( tab3 ); //[10, 25, 100, 310, 320, 400, 1200]
```

```js
const tab = [
    { name: "Marcin" , height : 183 },
    { name: "Ania" , height : 173 },
    { name: "Agnieszka" , height : 170 },
]

//dla sort spokojnie możemy używać funkcji anonimowej
tab.sort(function(a, b) {
    return a.height - b.height;
});

console.log(tab);
```

W powyższych przykładach sortowaliśmy po liczbach. Jeżeli podczas takiego sortowania chcielibyśmy porównać 2 teksty, odejmowanie tutaj nie zadziała prawidłowo (bo znowu wkradnie się porównywanie leksykalne). O wiele lepszym rozwiązaniem będzie użycie metody [localeCompare()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare):

```js
const tab = ["Marcin", "ania", "Bożena"]; //ania specjalnie z małej
tab.sort(function(a, b) {
    return a - b;
})
console.log(tab); //['Marcin', 'ania', 'Bożena']

tab.sort(function(a, b) {
    return a.localeCompare( b);
})
console.log(tab); //['ania', 'Bożena', 'Marcin']
```

Dzięki temu, że możemy przekazać własną funkcję, sortować możemy każdy rodzaj tablicy:

```js
const mails = [
    "marcin@wp.pl",
    "marcin@gmail.pl",
    "marcin@onet.pl",
    "marcin@interia.pl"
]

//sortuje po domenach
//z użyciem funkcji strzałkowej
mails.sort(function(a, b) {
    const aDomain = a.substring(a.indexOf("@") + 1);
    const bDomain = b.substring(b.indexOf("@") + 1);
    return aDomain.localeCompare(bDomain);
})

console.log(mails);
```

```js
const users = [
    {
        name : "Marcin",
        car : {
            name : "Toyota",
            age : 10
        }
    },
    {
        name : "Marcin",
        car : {
            name : "Fiat",
            age : 15
        }
    },
    {
        name : "Monika",
        car : {
            name : "BMW",
            age : 5
        }
    },
]

//sortuje po wieku samochodu
users.sort(function(a, b) {
    return a.car.age - b.car.age
});

console.log(users);
```

### Łączenie tablic <a href="#merge" id="merge"></a>

Do połączenia (scalenia) tablic nie możemy użyć zwykłego dodawania (tak samo nie możemy odejmować tablicy od tablicy):

```js
const tab1 = ["Ala", "Basia"];
const tab2 = ["Piotr", "Marcin"];
console.log(tab1 + tab2); //Ala,BasiaPiotr,Marcin
```

Wynikiem jest wartość typu string, co raczej nie jest prawidłowym rezultatem.

Aby połączyć ze sobą kilka tablic wykorzystamy funkcje **concat()**, która jako parametr przyjmuje jedną lub kilka tablic:

```js
const anim1 = ["Pies", "Kot"];
const anim2 = ["Słoń", "Wieloryb"];
const anim3 = ["Chomik ninja", "Świnka morderca"];

const table = anim1.concat(anim2);
console.log(table); //wypisze ["Pies", "Kot", "Słoń", "Wieloryb"]

const tableBig = anim1.concat(anim2, anim3);
console.log(tableBig); //wypisze ["Pies", "Kot", "Słoń", "Wieloryb", "Chomik ninja", "Świnka morderca"];
```

W dzisiejszych czasach o wiele przyjemniejszym sposobem jest użycie spread syntax:

```js
const anim1 = ["Pies", "Kot"];
const anim2 = ["Słoń", "Wieloryb"];
const table = [...anim1, ...anim2];
console.log(table)
```

### Funkcja slice() <a href="#slice" id="slice"></a>

Funkcja **slice(od, do\*)** tak samo jak przy stringach, zwraca kawałek tablicy na której została wywołana.

Pierwszy parametr wskazuje na indeks, od którego ma "wyciąć" elementy, a drugi wskazuje indeks do jakiego będziemy ciąć. Jeżeli nie podamy drugiej wartości, zostanie wycięty kawałek od danego indeksu do końca tablicy.

```js
const tab = ["Marcin", "Ania", "Agnieszka", "Monika", "Magda"];

const tab2 = tab.slice(0, 1);
console.log(tab2); //["Marcin"]
console.log(tab); //["Marcin", "Ania", "Agnieszka", "Monika", "Magda"]

const tab3 = tab.slice(2);
console.log(tab3); //["Agnieszka", "Monika", "Magda"]

const tab4 = tab.slice(0, 5);
console.log(tab4); //["Marcin", "Ania", "Agnieszka", "Monika", "Magda"]

const tab5 = tab.slice(-2); //od końca
console.log(tab5); //["Monika", "Magda"]

const tab6 = tab.slice(2, -1);
console.log(tab6); //["Agnieszka", "Monika"]
```

### Funkcja splice() <a href="#splice_delete" id="splice_delete"></a>

Funkcja **splice(index, ileUsunąć, nowyElement\*...)** służy zarówno do usuwania jak i wstawiania nowych elementów do tablicy.\
Parametr **index** określa miejsce w tablicy gdzie będziemy działać.\
Parametr **ileUsunąć** mówi ile elementów powinno być usuniętych z tablicy.\
Opcjonalne parametry **nowyElement** to element (lub kilka), który będzie wstawiany przed danym miejscem tablicy.

```js
const tab = ["Marcin", "Ania", "Agnieszka", "Monika"];

tab.splice(2, 1); //usuwam 1 element na indeksie 2
console.log(tab); //["Marcin", "Ania", "Monika"]
```

```js
const tab = ["Marcin", "Ania", "Agnieszka", "Monika"];

tab.splice(1, 0, "A") //nic nie usuwam na indeksie 1 i wstawiam przed niego nowy element
console.log(tab); //["Marcin", "A", "Ania", "Agnieszka", "Monika"]
```

```js
const tab = ["pies", "kot", "chomik", "aligator", "świnka", "kanarek"];
const index = tab.indexOf("aligator");

if (index !== -1) {
    tab.splice(index, 1);
    console.log(tab); //["pies", "kot", "chomik", "świnka", "kanarek"];
}
```

### Funkcja fill() <a href="#fill" id="fill"></a>

Funkcja **fill()** służy do wypełniania tablicy. Pierwszy jej parametr to wartość, którą zostanie wypełniona tablica. Dwa pozostałe opcjonalne parametry wskazują na indeks początku i końca wypełniania. Jeżeli nie podamy indeksu końcowego, tablica zostanie wypełniona od indeksu początkowego do swojego końca.

```js
const tab = new Array(20);
console.log(tab); //[empty x 20]
tab.fill("kot");
console.log(tab); //["kot", "kot", "kot", ...]


const tab2 = [];
tab2.length = 15;
console.log(tab2); //[empty x 15]
tab2.fill("kot", 2, 5);
console.log(tab2); //[empty × 2, "kot", "kot", "kot", empty × 10]


const tab3 = [1, 2, 3, 4, 5];
tab3.fill("pies", 2);
console.log(tab3); //[1, 2, "pies", "pies", "pies"]
```

### Przemieszczanie się po tablicy <a href="#for" id="for"></a>

Jedną z najwspanialszych rzeczy, jakie dają nam tablice to możliwość wykonywania masowych operacji na wszystkich elementach w tablicy.

Aby zrobić pętlę po tablicy możemy skorzystać z kilku zapisów. Jednym z nich jest użycie klasycznych pętli for/while:

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

for (let i=0; i<tab.length; i++) {
    console.log("licznik pętli: " + i); //0, 1...
    console.log(tab[i]); //"Marcin", "Ania"...
}
```

Widzisz jak się odwołuję do danego elementu tablicy? Skoro licznik `i` zwiększa się od `0` do `tab.length-1`, to mogę za jego pomocą pobierać kolejne elementy tablicy czyli `tab[0]`, `tab[1]`... i tak do `tab[tab.length-1]`

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

for (let i=0; i<tab.length; i++) {
    const el = tab[i];
    console.log(el.toUpperCase()); //"MARCIN", "ANIA"...
}
```

Oczywiście pętlę while też możemy zastosować, przy czym wykorzystanie jej dla iterowania po tablicy jest mało spotykane.

Iterowanie po tablicach to tak częsta rzecz, że w Javascript mamy na to kilka możliwości.

W dzisiejszych czasach możemy też zastosować o wiele przyjemniejszą pętlę `for of`, która automatycznie wyłapie nam odpowiedni element z tablicy (nie musimy odwoływać się poprzez `tab[i]`):

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

for (const el of tab) { //el to nazwa zmiennej wymyślona przez nas
    console.log(el); //"Marcin", "Ania"...
}

for (const xxx of tab) { //xxx to nazwa zmiennej wymyślona przez nas
    console.log(xxx.toUpperCase()); //"MARCIN"...
}

for (let lorem of tab) { //niektórzy używają tutaj let zamiast const
    console.log(lorem);
}
```

Pętla ta jest na tyle wygodna, że w kolejnych rozdziałach (szczególnie DOM) dość często będę z niej korzystał.

Innym sposobem wykonywania zbiorczych operacji na tablicach jest zastosowanie metod iteracyjnych takich jak `forEach()`. Omówimy je w kolejnym rozdziale.

### Tablice wielowymiarowe <a href="#tablice-wielowymiarowe" id="tablice-wielowymiarowe"></a>

Skoro tablice mogą w sobie trzymać dowolne wartości, mogą także przetrzymywać obiekty, lub... kolejne tablice. Taki twór zwie się **tablicami wielowymiarowymi**.

```js
const tab = [
    ["a1", "a2", "a3", "a4", "a5", "a6"],
    ["b1", "b2", "b3", "b4", "b5", "b6"],
    ["c1", "c2", "c3", "c4", "c5", "c6"],
]

console.log(tab[0]); //["a1", "a2", "a3", "a4", "a5", "a6"]
console.log(tab[0].length); //6
console.log(tab[0][1]); //"a2"
console.log(tab[2][3]); //"c4"
```

Do czego takie wielowymiarowe tablice mogą się przydać? Przykładowo do przetrzymywania danych:

```js
const tab = [
    ["Marcin", 183, "red", "kot"],
    ["Ania", 173, "blue", "pies"],
    ["Agnieszka", 170, "yellow", "świnka"]
]

console.log(`
    imię:       ${tab[0][0]}
    wzrost:     ${tab[0][1]}
    kolor:      ${tab[0][2]}
    zwierzak:   ${tab[0][3]}
`);
```

Powyższy przykład nie jest idealny. Trzymanie w taki sposób danych tego typu to proszenie się o problemy. Po pierwsze musimy pamiętać kolejność indeksów dla każdej danej. Druga sprawa to potencjalna możliwość zmiany kolejności indeksów. Problemy te rozwiązują obiekty, o których sobie jeszcze porozmawiamy.

Nie oznacza to jednak, że takie wielowymiarowe tablice nie mają zastosowania. Mają - i to całkiem duże.

Wystarczy chociażby spojrzeć praktycznie na dowolną grę. Większość map poziomów w takich grach zbudowana jest na bazie właśnie tablic wielowymiarowych, gdzie każdy indeks wewnętrznych tablic zawiera informację na temat użytej w danym miejscu grafiki czy wystąpienia ściany.

<figure><img src="https://kursjs.pl/kurs/super-podstawy/level-mario.png" alt=""><figcaption></figcaption></figure>

Tutaj mała ciekawostka. Wielu artystów nie tworzy takich tablic za pomocą kodu, a raczej korzysta ze specjalnych edytorów, które służą do układania grafiki na planszy. Są to np. <https://www.mapeditor.org/>, lub [tiled](https://www.mapeditor.org/), ale też wiele z popularnych narzędzi w świecie gamedevu ma swoje wbudowane edytory.

Spróbujmy wyświetlić jedną z takich przykładowych tablic na ekranie konsoli debuggera:

```js
const level = [
    [1, 1, 0, 0, 2, 2, 0, 0, 1, 1],
    [1, 0, 0, 0, 2, 2, 0, 0, 0, 1],
    [1, 0, 1, 1, 2, 2, 1, 1, 0, 1],
    [1, 0, 2, 2, 2, 2, 2, 2, 0, 1],
    [2, 2, 2, 2, 1, 1, 2, 2, 2, 2],
    [2, 2, 2, 2, 1, 1, 2, 2, 2, 2],
    [1, 0, 2, 2, 2, 2, 2, 2, 0, 1],
    [1, 0, 1, 1, 2, 2, 1, 1, 0, 1],
    [1, 0, 0, 0, 2, 2, 0, 0, 0, 1],
    [1, 1, 0, 0, 2, 2, 0, 0, 1, 1]
];


let str = "";

for (const subTab of level) {
    //pod subTab mamy każdą kolejną podtablicę

    for (const el of subTab) {
        switch (el) {
            case 0 : str += "🟩"; break;
            case 1 : str += "🟫"; break;
            case 2 : str += "⬛"; break;
        }
    }

    str += "\n";
}

console.log(str);
```

Otwórz konsolę debuggera i kliknij w poniższy przycisk:

klik

W naszym przypadku w tablicy są tylko wartości 0/1/2. W praktyce musiały by się tam znaleźć indeksy każdej grafiki użytej do zrobienia danej planszy, a i przydało by się tutaj dodać kilka funkcji rysujących realną planszę.

I tu się pojawia smutek autora. Chciałbym wam teraz pokazać jakiś bardziej realny przykład, ale wymagało by to sięgnięcia po bardziej zaawansowaną wiedzę, którą poznamy w późniejszych rozdziałach.

### Funkcja flat() <a href="#flat" id="flat"></a>

Jeżeli chcemy spłaszczyć wielowymiarową tablicę, zastosujemy funkcję [flat()](https://developer.mozilla.org/pl/docs/Web/JavaScript/Referencje/Obiekty/Array/flat). Jedyny jej parametr służy do określenia ile poziomów mamy spłaszczyć:

```js
const tab = [
    1,
    [2,3],
    [4,5,[6,7]],
    [[[8,9], [10,11]]]
]

console.log(tab.flat(1));
```

<figure><img src="https://kursjs.pl/kurs/super-podstawy/table-flat-1.png" alt=""><figcaption></figcaption></figure>

```js
const tab = [
    1,
    [2,3],
    [4,5,[6,7]],
    [[[8,9], [10,11]]]
]

console.log(tab.flat(2));
```

<figure><img src="https://kursjs.pl/kurs/super-podstawy/table-flat-2.png" alt=""><figcaption></figcaption></figure>

Jeżeli chcemy mieć pewność, że uzyskamy płaską 1 wymiarową tablicę, jako liczbę poziomów podajmy **Infinity**:

```js
const tab = [
    1,
    [2,3],
    [4,5,[6,7]],
    [[[8,9], [10,11]]]
]

console.log(tab.flat(Infinity)); //[1,2,3,4,5,6,7,8,9,10,11]
```

### Array.from() <a href="#array.from" id="array.from"></a>

Funkcja `Array.from(ob, map*, this*)` służy do tworzenia tablic z obiektów tablico podobnych. Obiekty takie są podobne do tablic, ale równocześnie nimi nie są. Przykładem takiego obiektu są kolekcje elementów pobranych ze strony, argumenty funkcji (arguments), czy np. classList dla elementów na stronie. Będziemy się o nich uczyć w kolejnych rozdziałach.

```js
const ob = {
    0 : "ala",
    1 : "bela",
    length: 2
}

console.log(Array.from(ob)); //["ala", "bela"]
```

```js
//pobieram kolekcję buttonów ze strony
const buttons = document.querySelectorAll("button");
console.log(buttons); //NodeList [button, button...]

const tab = Array.from(buttons);
console.log(tab); //Array [button, button...]
```

Drugi opcjonalny parametr tej funkcji może zawierać funkcję map() dla tablic:

```js
const ob = {
    0 : "ala",
    1 : "bela",
    length: 2
}

const tab = Array.from(ob, function(el) {
    return el.toUpperCase()
});
console.log(tab); //["ALA", "BELA"]
```

Trzeci - równie opcjonalny parametr wskazuje na this. Działa on podobnie do stosowanego w przypadku metod dla tablic. W praktyce w większości przypadków używany jest tylko pierwszy parametr czyli obiekt, który poddajemy konwersji (tak jak w pierwszym przykładzie).

W dzisiejszych czasach w wielu przypadkach zamiast powyższej metody możemy skorzystać ze spread syntax:

```js
const buttons = document.querySelectorAll("button");
console.log(buttons); //NodeList [button, button...]

const buttonsTabA = [...buttons]; //array
const buttonsTabB = Array.from(buttons); //array
```

Różnica między spread syntax a `Array.from()` jest taka, że tą drugą możemy zastosować dla obiektów tablico podobnych, które nie mają zaimplementowanej własnej funkcji iterującej:

```js
const ob = { 0: "a", 1: "b", length: 2 };

console.log(Array.from(ob)); //["a", "b"]

console.log([...ob]); //ob[Symbol.iterator] is not a function
```

Spokojnie. Nie musisz się teraz skupiać na bardzo dokładnym poznaniu każdego aspektu działania tej metody. Wszystko przyjdzie z czasem.

### Funkcje toSorted(), toReversed(), toSpliced(), with() <a href="#tosorted" id="tosorted"></a>

W EcmaScript 2023 wprowadzono dla tablic nowe metody takie jak **toSorted()**, **toReversed()**, **toSpliced()** i **with()**.

Ich działanie jest takie samo jak powyższych, z tym że nie modyfikują oryginalnej tablicy, a zwracają zmienioną.

```js
{
    const tab = [1, 2, 3, 4];
    const newTab = tab.reverse(); //metoda zmienia oryginalną tablicę
    console.log(tab); //[4, 3, 2, 1]
    console.log(newTab); //[4, 3, 2, 1]
}

{
    const tab = [1, 2, 3, 4];
    //nowa metoda zwraca odwróconą tablicę ale nie zmienia oryginalnej
    const newTab = tab.toReversed();
    console.log(tab); //[1, 2, 3, 4]
    console.log(newTab); //[4, 3, 2, 1]
}
```

```js
{
    const tab = [10, 1.4, 22, 2];
    const newTab = tab.sort(function(a, b) {
        return a - b;
    })
    console.log(tab); //[1.4, 2, 10, 22]
    console.log(newTab); //[1.4, 2, 10, 22]
}

{
    const tab = [10, 1.4, 22, 2];
    const newTab = tab.toSorted(function(a, b) {
        return a - b;
    })
    console.log(tab); //[10, 1.4, 22, 2]
    console.log(newTab); //[1.4, 2, 10, 22]
}
```

Pisząc dobre skrypty staraj się raczej nie modyfikować oryginalnych danych, a raczej staraj się pracować na ich kopi. Stad funkcje te są bardzo pomocne.

W momencie pisania tego tekstu funkcje te są dość nowe i nie działają jeszcze w Firefoxie. Podejrzewam, że wielu programistów zostanie w takich przypadkach przy tworzeniu kopi za pomocą spread operator:

```js
{
    const tab = [1, 2, 3, 4];
    //za pomocą spread operator stworzyłem kopię oryginalnej tablicy, a potem ją odwróciłem
    const newTab = [...tab].reverse();
    console.log(tab); //[1, 2, 3, 4]
    console.log(newTab); //[4, 3, 2, 1]
}

{
    const tab = [10, 1.4, 22, 2];
    const newTab = [...tab].sort(function(a, b) {
        return a - b;
    })
    console.log(tab); //[10, 1.4, 22, 2]
    console.log(newTab); //[1.4, 2, 10, 22]
}
```

W EcmaScript 2023 wprowadzono też funkcję **with(index, value)**, która zwraca nową tablicę ze zmienionym elementem na danym miejscu. Parametr `index` oznacza miejsce modyfikacji. Jeżeli podana jest ujemna wartość, miejsce liczone jest od końca tablicy. Parametr value oznacza nową wartość.

```js
const tab = [1, 2, 3, 4, 5];
const newTab = tab.with(1, 10);
console.log(newTab); //[1, 10, 3, 4, 5];

const tabB = ["Ala", "Beata", "Cecylia", "Dagmara", "Ela"];
const newTabB = tabB.with(-2, "Karol");
console.log(newTabB); //['Ala', 'Beata', 'Cecylia', 'Karol', 'Ela']
```

W momencie pisania tego tekstu trzeba uważać z jej użyciem, ponieważ metoda ta nie działa jeszcze w Firefoxie. Nic jednak nie stoi na przeszkodzie by użyć klasycznych metod:

```js
const tab = [1, 2, 3, 4, 5];
const newTab = [...tab].splice(1, 1, 10);
console.log(newTab); //[1, 10, 3, 4, 5];
```
