23. ledna 2025

Metody polí

Pole poskytují mnoho metod. Pro zjednodušení jsme je v této kapitole rozdělili do několika skupin.

Přidávání a odebírání prvků

Známe už metody, které přidávají a odebírají prvky na začátku nebo na konci pole:

  • pole.push(...prvky) – přidává prvky na konec,
  • pole.pop() – vybírá prvek z konce,
  • pole.shift() – vybírá prvek ze začátku,
  • pole.unshift(...prvky) – přidává prvky na začátek.

Zde uvedeme několik dalších.

splice

Jak smazat prvek z pole?

Pole jsou objekty, takže se můžeme pokusit použít delete:

let pole = ["Já", "jdu", "domů"];

delete pole[1]; // odstraníme "jdu"

alert( pole[1] ); // undefined

// nyní pole = ["Já",  , "domů"];
alert( pole.length ); // 3

Prvek byl odstraněn, ale pole má stále 3 prvky. Vidíme, že pole.length == 3.

To je přirozené, poněvadž delete obj.klíč odstraňuje hodnotu podle klíč. To je vše, co udělá. Pro objekty je to dobře. Ale u polí zpravidla chceme, aby se ostatní prvky posunuly a obsadily uvolněné místo. Očekáváme, že nyní budeme mít kratší pole.

Měli bychom tedy použít speciální metody.

Metoda pole.splice je jako švýcarský nůž pro pole. Umí všechno: vkládat, odstraňovat i nahrazovat prvky.

Syntaxe je:

pole.splice(začátek[, početSmazaných, prvek1, ..., prvekN])

Modifikuje pole od indexu začátek: napřed odstraní početSmazaných prvků a pak vloží na jejich místo prvek1, ..., prvekN. Vrátí pole odstraněných prvků.

Tato metoda je snadno pochopitelná na příkladech.

Začněme s mazáním:

let pole = ["Já", "studuji", "JavaScript"];

pole.splice(1, 1); // od indexu 1 odstraníme 1 prvek

alert( pole ); // ["Já", "JavaScript"]

Snadné, že? Počínajíc indexem 1 metoda odstranila 1 prvek.

V dalším příkladu odstraníme 3 prvky a nahradíme je dvěma jinými:

let pole = ["Já", "studuji", "JavaScript", "právě", "teď"];

// odstraníme první 3 prvky a nahradíme je dvěma jinými
pole.splice(0, 3, "Zatancujme", "si");

alert( pole ) // nyní ["Zatancujme", "si", "právě", "teď"]

Zde vidíme, že splice vrací pole odstraněných prvků:

let pole = ["Já", "studuji", "JavaScript", "právě", "teď"];

// odstraníme první 2 prvky
let odstraněno = pole.splice(0, 2);

alert( odstraněno ); // "Já", "studuji" <-- pole odstraněných prvků

Metoda splice dokáže také vkládat prvky bez odstraňování. Toho dosáhneme tak, že nastavíme početSmazaných na 0:

let pole = ["Já", "studuji", "JavaScript"];

// od indexu 2
// smažeme 0 prvků
// pak vložíme "složitý" a "jazyk"
pole.splice(2, 0, "složitý", "jazyk");

alert( pole ); // "Já", "studuji", "složitý", "jazyk", "JavaScript"
Záporné indexy jsou povoleny

Zde i v jiných metodách polí jsou povoleny záporné indexy. Ty specifikují pozici od konce pole, například:

let pole = [1, 2, 5];

// od indexu -1 (jeden krok před koncem)
// smažeme 0 prvků,
// pak vložíme 3 a 4
pole.splice(-1, 0, 3, 4);

alert( pole ); // 1,2,3,4,5

slice

Metoda pole.slice je mnohem jednodušší než podobně vypadající pole.splice.

Syntaxe je:

pole.slice([začátek], [konec])

Vrátí nové pole, do něhož zkopíruje všechny prvky od indexu začátek do indexu konec (konec není zahrnut). Jak začátek, tak konec mohou být záporné. V tom případě se pozice počítá od konce pole.

Podobá se řetězcové metodě str.slice, ale místo podřetězců vytváří podpole.

Například:

let pole = ["t", "e", "s", "t"];

alert( pole.slice(1, 3) ); // e,s (kopíruje od 1 do 3)

alert( pole.slice(-2) ); // s,t (kopíruje od -2 do konce)

Můžeme ji volat i bez argumentů: pole.slice() vytvoří kopii pole. To se často používá k vytvoření kopie pole pro další transformace, které nemají ovlivnit původní pole.

concat

Metoda pole.concat vytvoří nové pole, které obsahuje hodnoty z jiných polí a další prvky.

Syntaxe je:

pole.concat(arg1, arg2...)

Přijímá libovolný počet argumentů – mohou jimi být pole nebo hodnoty.

Výsledkem je nové pole, které obsahuje prvky z pole, pak arg1, arg2 atd.

Je-li argument argN pole, pak se zkopírují všechny jeho prvky. V opačném případě se zkopíruje sám argument.

Příklad:

let pole = [1, 2];

// vytvoříme pole z: pole a [3,4]
alert( pole.concat([3, 4]) ); // 1,2,3,4

// vytvoříme pole z: pole a [3,4] a [5,6]
alert( pole.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6

// vytvoříme pole z: pole a [3,4], pak přidáme hodnoty 5 a 6
alert( pole.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6

Běžně kopíruje prvky jen z polí. Ostatní objekty, i ty, které vypadají jako pole, se přidají jako celek:

let pole = [1, 2];

let jakoPole = {
  0: "něco",
  length: 1
};

alert( pole.concat(jakoPole) ); // 1,2,[object Object]

…Jestliže však objekt podobný poli má speciální vlastnost Symbol.isConcatSpreadable, pak s ním metoda concat zachází jako s polem – místo objektu se přidají jeho prvky:

let pole = [1, 2];

let jakoPole = {
  0: "něco",
  1: "jiného",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};

alert( pole.concat(jakoPole) ); // 1,2,něco,jiného

Iterace: forEach

Metoda pole.forEach umožňuje pro každý prvek pole volat zadanou funkci.

Syntaxe:

pole.forEach(function(prvek, index, pole) {
  // ... provádí něco s prvkem
});

Například tohle zobrazí každý prvek pole:

// pro každý prvek volá alert
["Bilbo", "Gandalf", "Nazgúl"].forEach(alert);

A tento kód podrobně vypíše pozice prvků v cílovém poli:

["Bilbo", "Gandalf", "Nazgúl"].forEach((prvek, index, pole) => {
  alert(`${prvek} je na indexu ${index} v poli ${pole}`);
});

Pokud funkce vrátí nějaký výsledek, je zahozen a ignorován.

Hledání v poli

Uveďme nyní metody, které prohledávají pole.

indexOf/lastIndexOf a includes

Metody pole.indexOf a pole.includes mají podobnou syntaxi a dělají v zásadě totéž, jako jejich řetězcové protějšky, ale místo znaků pracují nad prvky pole:

  • pole.indexOf(prvek, odkud) – hledá prvek počínajíc indexem odkud a vrátí index, na němž byl prvek nalezen, anebo -1, když nalezen nebyl.
  • pole.includes(prvek, odkud) – hledá prvek počínajíc indexem odkud a vrátí true, pokud byl nalezen.

Tyto metody se obvykle používají jen s jedním argumentem: tím je prvek, který se má hledat. Standardně se hledá od začátku pole.

Příklad:

let pole = [1, 0, false];

alert( pole.indexOf(0) ); // 1
alert( pole.indexOf(false) ); // 2
alert( pole.indexOf(null) ); // -1

alert( pole.includes(1) ); // true

Prosíme všimněte si, že metoda indexOf používá striktní porovnávání ===. Hledáme-li tedy false, najde přesně false a ne nulu.

Pokud chceme jen ověřit, zda prvek je obsažen v poli, a nepotřebujeme jeho index, dává se přednost metodě pole.includes.

Metoda pole.lastIndexOf je totéž jako indexOf, ale hledá zprava doleva.

let ovoce = ['Jablko', 'Pomeranč', 'Jablko']

alert( ovoce.indexOf('Jablko') ); // 0 (první Jablko)
alert( ovoce.lastIndexOf('Jablko') ); // 2 (poslední Jablko)
Metoda includes zpracovává správně NaN

Drobný, ale pozoruhodný rozdíl metody includes je také v tom, že na rozdíl od indexOf správně zpracovává NaN:

const pole = [NaN];
alert( pole.indexOf(NaN) ); // -1 (mělo by být 0, ale rovnost === pro NaN nefunguje)
alert( pole.includes(NaN) ); // true (správně)

Je to proto, že metoda includes byla do JavaScriptu přidána mnohem později a vnitřně používá aktuálnější porovnávací algoritmus.

find a findIndex/findLastIndex

Představme si, že máme pole objektů. Jak najdeme objekt, pro který platí specifická podmínka?

Tady se nám hodí metoda pole.find(fn).

Syntaxe je:

let výsledek = pole.find(function(prvek, index, pole) {
  // jestliže funkce vrátí true, bude vrácen prvek a iterace se zastaví
  // pokud funkce vrátí samá false, bude vráceno undefined
});

Funkce je volána na prvcích pole postupně na jednom za druhým:

  • prvek je prvek.
  • index je jeho index.
  • pole je samotné pole.

Jestliže vrátí true, hledání se zastaví a vrátí se prvek. Není-li nic nalezeno, vrátí se undefined.

Například máme pole uživatelů, každý má vlastnosti id a jméno. Najděme toho, který má id == 1:

let uživatelé = [
  {id: 1, jméno: "Jan"},
  {id: 2, jméno: "Petr"},
  {id: 3, jméno: "Marie"}
];

let uživatel = uživatelé.find(prvek => prvek.id == 1);

alert(uživatel.jméno); // Jan

V reálném životě se pole objektů běžně používají, takže metoda find je velmi užitečná.

Všimněte si, že v tomto příkladu poskytujeme metodě find funkci prvek => prvek.id == 1 s jedním argumentem. To je typické, ostatní argumenty této funkce se používají málokdy.

Metoda pole.findIndex má stejnou syntaxi, ale namísto samotného prvku vrací index, na němž byl prvek nalezen. Jestliže nebylo nalezeno nic, vrátí hodnotu -1.

Metoda pole.findLastIndex je podobná metodě findIndex, ale hledá zprava doleva, podobně jako lastIndexOf.

Příklad:

let uživatelé = [
  {id: 1, jméno: "Jan"},
  {id: 2, jméno: "Petr"},
  {id: 3, jméno: "Marie"},
  {id: 4, jméno: "Jan"}
];

// Najde index prvního Jana
alert(uživatelé.findIndex(uživatel => uživatel.jméno == 'Jan')); // 0

// Najde index posledního Jana
alert(uživatelé.findLastIndex(uživatel => uživatel.jméno == 'Jan')); // 3

filter

Metoda find najde jediný (první) prvek, který způsobí, že funkce vrátí true.

Jestliže takových prvků může být více, můžeme použít metodu pole.filter(fn).

Její syntaxe je podobná find, ale filter vrací pole všech odpovídajících prvků:

let výsledky = pole.filter(function(prvek, index, pole) {
  // vrátí-li true, prvek se vloží metodou push do výsledků a iterace pokračuje
  // není-li nic nalezeno, je vráceno prázdné pole
});

Příklad:

let uživatelé = [
  {id: 1, jméno: "Jan"},
  {id: 2, jméno: "Petr"},
  {id: 3, jméno: "Marie"}
];

// vrátí pole prvních dvou uživatelů
let nějacíUživatelé = uživatelé.filter(prvek => prvek.id < 3);

alert(nějacíUživatelé.length); // 2

Transformace polí

Přejděme nyní k metodám, které pole transformují a přeskupují.

map

Metoda pole.map je jedna z nejužitečnějších a nejčastěji používaných.

Volá zadanou funkci pro každý prvek pole a vrací pole výsledků.

Syntaxe je:

let výsledek = pole.map(function(prvek, index, pole) {
  // vrátí novou hodnotu místo prvku
});

Například zde přetransformujeme každý prvek na jeho délku:

let délky = ["Bilbo", "Gandalf", "Nazgúl"].map(prvek => prvek.length);
alert(délky); // 5,7,6

sort(fn)

Volání metody pole.sort() seřadí pole uvnitř a změní pořadí jeho prvků.

Vrací také seřazené pole, ale návratová hodnota se obvykle ignoruje, jelikož je modifikováno samotné pole.

Příklad:

let pole = [ 1, 2, 15 ];

// metoda přehází obsah pole
pole.sort();

alert( pole );  // 1, 15, 2

Všimli jste si na výstupu něčeho divného?

Seřazené pole je 1, 15, 2. To není správně. Ale proč?

Prvky jsou standardně řazeny jako řetězce.

Všechny prvky bez výjimky se při porovnávání převádějí na řetězce. Pro řetězce se použije lexikografické řazení a skutečně "2" > "15".

Abychom použili naše vlastní řazení, musíme jako argument pole.sort() poskytnout funkci.

Tato funkce by měla porovnávat dvě libovolné hodnoty a vracet:

function porovnej(a, b) {
  if (a > b) return 1; // je-li první hodnota větší než druhá
  if (a == b) return 0; // jsou-li si hodnoty rovny
  if (a < b) return -1; // je-li první hodnota menší než druhá
}

Například když řadíme čísla:

function porovnejČísla(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}

let pole = [ 1, 2, 15 ];

pole.sort(porovnejČísla);

alert(pole);  // 1, 2, 15

Nyní to funguje tak, jak jsme zamýšleli.

Udělejme nyní krok stranou a zamysleme se nad tím, co se děje. pole může obsahovat cokoli, že? Může obsahovat čísla, řetězce, objekty, zkrátka cokoli. Máme sadu nějakých prvků. Abychom ji seřadili, potřebujeme řadicí funkci, která umí její prvky porovnat. Standardní řazení je řetězcové.

Metoda pole.sort(fn) implementuje generický algoritmus řazení. Nemusíme se zajímat o to, jak vnitřně funguje (většinou je to optimalizované rychlé řazení (quicksort) nebo Timsort). Projde pole, porovná jeho prvky poskytnutou funkcí a seřadí je. Vše, co potřebujeme, je poskytnout funkci fn, která provede porovnání.

Mimochodem, jestliže chceme vědět, které prvky se porovnávají – nic nám nebrání je zobrazit:

[1, -2, 15, 2, 0, 8].sort(function(a, b) {
  alert( a + " <> " + b );
  return a - b;
});

Algoritmus může v tomto procesu porovnat jeden prvek s několika jinými, ale snaží se učinit co nejméně porovnání.

Porovnávací funkce může vracet číslo

Ve skutečnosti se od porovnávací funkce vyžaduje jen to, aby vrátila kladné číslo, když říká „větší“, a záporné, když říká „menší“.

To nám umožňuje psát kratší funkce:

let pole = [ 1, 2, 15 ];

pole.sort(function(a, b) { return a - b; });

alert(pole);  // 1, 2, 15
Nejlepší jsou šipkové funkce

Vzpomínáte si na šipkové funkce? Můžeme je zde použít, aby kód řazení byl úhlednější:

pole.sort( (a, b) => a - b );

Funguje to přesně stejně jako výše uvedená delší verze.

Pro řetězce používejte localeCompare

Vzpomínáte si na porovnávací algoritmus pro řetězce? Standardně porovnává písmena podle jejich kódů.

Pro mnoho abeced je lepší používat metodu řetězec.localeCompare, která seřadí správně písmena, např. Č.

Například seřaďme několik států v češtině:

let státy = ['Česko', 'Andorra', 'Vietnam'];

alert( státy.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Česko (špatně)

alert( státy.sort( (a, b) => a.localeCompare(b) ) ); // Andorra, Česko, Vietnam (správně!)

reverse

Metoda pole.reverse převrátí pořadí prvků pole.

Příklad:

let pole = [1, 2, 3, 4, 5];
pole.reverse();

alert( pole ); // 5,4,3,2,1

I ona vrací převrácené pole.

split a join

Máme zde situaci z běžného života. Píšeme aplikaci pro posílání zpráv a uživatel zadá seznam příjemců oddělených čárkou: Jan, Petr, Marie. Pro nás by však bylo daleko pohodlnější mít pole jmen než jediný řetězec. Jak je získat?

Přesně tohle dělá metoda řetězec.split(oddělovač), která rozdělí řetězec na pole podle zadaného oddělovače oddělovač.

V níže uvedeném příkladu oddělujeme čárkou, po níž následuje mezera:

let jména = 'Bilbo, Gandalf, Nazgúl';

let pole = jména.split(', ');

for (let jméno of pole) {
  alert( `Zpráva pro ${jméno}.` ); // Zpráva pro Bilbo (a další jména)
}

Metoda split má nepovinný druhý číselný argument – limit délky pole. Pokud je uveden, pak se po dosažení tohoto limitu další prvky ignorují. V praxi se však používá jen zřídka:

let pole = 'Bilbo, Gandalf, Nazgúl, Saruman'.split(', ', 2);

alert(pole); // Bilbo, Gandalf
Rozdělení na písmena

Volání split(s) s prázdným s rozdělí řetězec na pole písmen:

let řetězec = "test";

alert( řetězec.split('') ); // t,e,s,t

Metoda pole.join(spojka) provádí opak metody split. Vytvoří řetězec z prvků pole, které budou mezi sebou spojeny řetězcem spojka.

Příklad:

let pole = ['Bilbo', 'Gandalf', 'Nazgúl'];

let řetězec = pole.join(';'); // spojíme pole do řetězce pomocí ;

alert( řetězec ); // Bilbo;Gandalf;Nazgúl

reduce/reduceRight

Když potřebujeme procházet prvky pole – můžeme použít forEach, for nebo for..of.

Když potřebujeme procházet prvky a pro každý prvek vrátit nějaká data – můžeme použít map.

Do této skupiny patří i metody pole.reduce a pole.reduceRight, ale ty jsou trochu záludnější. Používají se k výpočtu jediné hodnoty závisející na poli.

Syntaxe je:

let hodnota = pole.reduce(function(akumulátor, prvek, index, pole) {
  // ...
}, [počátečníHodnota]);

Funkce se volá postupně na všech prvcích pole a svůj výsledek „přenáší“ do dalšího volání.

Argumenty:

  • akumulátor – výsledek předchozího volání funkce, napoprvé se rovná počátečníHodnota (je-li počátečníHodnota uvedena).
  • prvek – je aktuální prvek pole.
  • index – je jeho pozice.
  • pole – je pole.

Poté, co je funkce provedena, se výsledek předchozího volání funkce předá jako první argument v dalším volání.

První argument je tedy v podstatě akumulátor, do něhož se ukládá kombinovaný výsledek ze všech předchozích volání. Nakonec se stane výsledkem funkce reduce.

Zní to složitě?

Nejjednodušší způsob, jak to pochopit, je příklad.

Zde získáme součet celého pole na jediném řádku:

let pole = [1, 2, 3, 4, 5];

let výsledek = pole.reduce((součet, aktuální) => součet + aktuální, 0);

alert(výsledek); // 15

Funkce předaná do reduce používá jen 2 argumenty, to zpravidla stačí.

Podívejme se na podrobnosti toho, co se děje.

  1. Při prvním průběhu je součet roven hodnotě počátečníHodnota (poslední argument reduce), tedy 0, a aktuální je první prvek pole, tedy 1. Výsledek funkce je tedy 1.
  2. Při druhém průběhu součet = 1, přičteme k němu druhý prvek pole (2) a vrátíme výsledek.
  3. Při třetím průběhu součet = 3, přičteme k němu další prvek, a tak dále…

Průběh výpočtu:

Nebo ve formě tabulky, v níž každý řádek představuje volání funkce na dalším prvku pole:

součet aktuální výsledek
první volání 0 1 1
druhé volání 1 2 3
třetí volání 3 3 6
čtvrté volání 6 4 10
páté volání 10 5 15

Tady jasně vidíme, jak se výsledek předchozího volání stává prvním argumentem následujícího.

Můžeme také vypustit počáteční hodnotu:

let pole = [1, 2, 3, 4, 5];

// z reduce je odstraněna počáteční hodnota (žádná 0)
let výsledek = pole.reduce((součet, aktuální) => součet + aktuální);

alert( výsledek  ); // 15

Výsledek je stejný. Je to proto, že není-li uvedena počáteční hodnota, reduce vezme jako počáteční hodnotu první prvek pole a zahájí iteraci až od druhého.

Výpočetní tabulka je stejná jako výše uvedená, jen bez prvního řádku.

Takové použití však vyžaduje extrémní pozornost. Je-li pole prázdné, pak volání reduce bez počáteční hodnoty ohlásí chybu.

Příklad:

let pole = [];

// Chyba: Reduce of empty array with no initial value
// kdyby počáteční hodnota existovala, reduce by pro prázdné pole vrátila ji.
pole.reduce((součet, aktuální) => součet + aktuální);

Doporučuje se tedy počáteční hodnotu vždy uvádět.

Metoda pole.reduceRight dělá totéž, ale postupuje zprava doleva.

Array.isArray

Pole netvoří samostatný jazykový typ, ale jsou založena na objektech.

Proto typeof nedokáže rozlišit planý objekt od pole:

alert(typeof {}); // object
alert(typeof []); // object (totéž)

…Pole se však používají natolik často, že pro ně existuje speciální metoda: Array.isArray(hodnota). Vrátí true, jestliže hodnota je pole, a false jinak.

alert(Array.isArray({})); // false

alert(Array.isArray([])); // true

Většina metod podporuje „thisArg“

Téměř všechny metody polí, které volají funkce – např. find, filter, map, s významnou výjimkou metody sort, přijímají nepovinný další parametr thisArg.

Tento parametr nebyl vysvětlen ve výše uvedených podkapitolách, protože se používá jen zřídka. Ale pro úplnost jej musíme uvést.

Zde je úplná syntaxe těchto metod:

pole.find(funkce, thisArg);
pole.filter(funkce, thisArg);
pole.map(funkce, thisArg);
// ...
// thisArg je nepovinný poslední argument

Hodnota parametru thisArg se ve funkci funkce stane this.

Například zde použijeme metodu objektu armáda jako filtr a thisArg předá kontext:

let armáda = {
  minVěk: 18,
  maxVěk: 27,
  můžeVstoupit(uživatel) {
    return uživatel.věk >= this.minVěk && uživatel.věk < this.maxVěk;
  }
};

let uživatelé = [
  {věk: 16},
  {věk: 20},
  {věk: 23},
  {věk: 30}
];

// najdeme uživatele, pro které armáda.můžeVstoupit vrátí true
let vojáci = uživatelé.filter(armáda.můžeVstoupit, armáda);

alert(vojáci.length); // 2
alert(vojáci[0].věk); // 20
alert(vojáci[1].věk); // 23

Kdybychom v uvedeném příkladu použili uživatelé.filter(armáda.můžeVstoupit), pak by armáda.můžeVstoupit byla volána jako samostatná funkce, v níž by bylo this=undefined, což by okamžitě vedlo k chybě.

Volání uživatelé.filter(armáda.můžeVstoupit, armáda) můžeme nahradit za uživatelé.filter(uživatel => armáda.můžeVstoupit(uživatel)), které by udělalo totéž. Tato varianta se používá častěji, jelikož pro většinu lidí je trochu srozumitelnější.

Shrnutí

Přehled metod polí:

  • Pro přidávání a odebírání prvků:

    • push(...prvky) – přidá prvky na konec.
    • pop() – vyjme prvek z konce.
    • shift() – vyjme prvek ze začátku.
    • unshift(...prvky) – přidá prvky na začátek.
    • splice(pozice, početSmazaných, ...prvky) – na indexu pozice smaže početSmazaných prvků a vloží prvky.
    • slice(začátek, konec) – vytvoří nové pole, zkopíruje do něj prvky od indexu začátek do indexu konec (kromě něj).
    • concat(...prvky) – vrátí nové pole: zkopíruje všechny prvky z aktuálního pole a přidá do něj prvky. Je-li kterýmkoli prvkem z prvky pole, pak se místo něj přidají jeho prvky.
  • Pro hledání mezi prvky:

    • indexOf/lastIndexOf(prvek, pozice) – hledá prvek počínajíc pozicí pozice, vrátí jeho index nebo -1, pokud není nalezen.
    • includes(hodnota) – jestliže pole obsahuje hodnotu hodnota, vrátí true, jinak vrátí false.
    • find/filter(funkce) – filtruje prvky podle zadané funkce, vrátí první hodnotu/všechny hodnoty, na níž/nichž funkce vrátila true.
    • findIndex je jako find, ale namísto hodnoty vrátí index.
  • Pro procházení prvků:

    • forEach(funkce) – volá funkce pro každý prvek, nic nevrací.
  • Pro transformaci pole:

    • map(funkce) – vytvoří nové pole z výsledků volání funkce pro každý prvek.
    • sort(funkce) – seřadí prvky uvnitř pole, pak toto pole vrátí.
    • reverse() – převrátí pořadí prvků uvnitř pole, pak toto pole vrátí.
    • split/join – převádí řetězec na pole a zpět.
    • reduce/reduceRight(funkce, počátečníHodnota) – vypočítá z pole jedinou hodnotu tak, že volá funkce pro každý prvek a předává mezivýsledek mezi voláními.
  • Ostatní:

    • Array.isArray(hodnota) ověří, zda hodnota je pole. Pokud ano, vrátí true, jinak vrátí false.

Prosíme všimněte si, že metody sort, reverse a splice modifikují samotné pole.

Tyto metody jsou nejpoužívanější a v 99% případů dostačují. Existuje však i několik dalších:

  • pole.some(fn)/pole.every(fn) prověří pole.

    Na každém prvku pole je volána funkce fn, podobně jako u funkce map. Jestliže některý výsledek je/všechny výsledky jsou true, vrátí true, jinak vrátí false.

    Tyto metody se chovají obdobně jako operátory || a &&: jestliže fn vrátí pravdivou hodnotu, pole.some() okamžitě vrátí true a zastaví procházení zbývajících prvků; jestliže fn vrátí nepravdivou hodnotu, pole.every() okamžitě vrátí false a rovněž zastaví procházení zbývajících prvků.

    Pomocí every můžeme porovnávat pole:

    function poleSeRovnají(pole1, pole2) {
      return pole1.length === pole2.length && pole1.every((hodnota, index) => hodnota === pole2[index]);
    }
    
    alert( poleSeRovnají([1, 2], [1, 2])); // true
  • pole.fill(hodnota, začátek, konec) – vyplní pole opakující se hodnotou hodnota od indexu začátek do indexu konec.

  • pole.copyWithin(cíl, začátek, konec) – zkopíruje prvky pole od pozice začátek do pozice konec do téhož pole na pozici cíl (existující prvky přepíše).

  • pole.flat(hloubka)/pole.flatMap(fn) vytvoří nové jednorozměrné pole z vícerozměrného.

Pro úplný seznam nahlédněte do manuálu.

Na první pohled se může zdát, že je tady spousta metod a je těžké si je pamatovat. Ve skutečnosti je to však mnohem snazší.

Projděte si přehled metod, abyste o nich věděli. Pak vyřešte úlohy v této kapitole, abyste se pocvičili. Tak získáte s metodami polí zkušenosti.

Když později budete potřebovat udělat něco s polem a nebudete vědět jak – přijďte sem, podívejte se do přehledu a najděte správnou metodu. Příklady vám pomohou napsat ji správně. Brzy si budete metody automaticky pamatovat, aniž by vás to stálo nějakou zvláštní námahu.

Úlohy

důležitost: 5

Napište funkci camelizace(řetězec), která změní slova oddělená pomlčkou, např. „můj-krátký-řetězec“, na velbloudí notaci „můjKrátkýŘetězec“.

To znamená, že odstraní všechny pomlčky a u všech slov za pomlčkami změní první písmeno na velké.

Příklad:

camelizace("background-color") == 'backgroundColor';
camelizace("list-style-image") == 'listStyleImage';
camelizace("-webkit-transition") == 'WebkitTransition';

P.S. Rada: použijte split pro rozdělení řetězce na pole, přeměňte je a spojte zpět pomocí join.

Otevřít pískoviště s testy.

function camelizace(řetězec) {
  return řetězec
    .split('-') // rozdělí 'mé-dlouhé-slovo' na pole ['mé', 'dlouhé', 'slovo']
    .map(
      // převede první písmena všech prvků pole kromě prvního na velká
      // převede ['mé', 'dlouhé', 'slovo'] na ['mé', 'Dlouhé', 'Slovo']
      (slovo, index) => index == 0 ? slovo : slovo[0].toUpperCase() + slovo.slice(1)
    )
    .join(''); // spojí ['mé', 'Dlouhé', 'Slovo'] do 'méDlouhéSlovo'
}

Otevřít řešení s testy na pískovišti.

důležitost: 4

Napište funkci filtrujPodleRozsahu(pole, a, b), která obdrží pole pole, najde prvky, jejichž hodnoty jsou vyšší nebo rovny a a nižší nebo rovny b a vrátí výsledek jako pole.

Funkce by neměla pole modifikovat. Měla by vrátit nové pole.

Příklad:

let pole = [5, 3, 8, 1];

let filtrovanéPole = filtrujPodleRozsahu(pole, 1, 4);

alert( filtrovanéPole ); // 3,1 (odpovídající hodnoty)

alert( pole ); // 5,3,8,1 (nezměněno)

Otevřít pískoviště s testy.

function filtrujPodleRozsahu(pole, a, b) {
  // závorky kolem výrazu jsou přidány pro lepší čitelnost
  return pole.filter(prvek => (a <= prvek && prvek <= b));
}

let testovacíPole = [5, 3, 8, 1];

let filtrovanéPole = filtrujPodleRozsahu(testovacíPole, 1, 4);

alert( filtrovanéPole ); // 3,1 (odpovídající hodnoty)

alert( testovacíPole ); // 5,3,8,1 (nezměněno)

Otevřít řešení s testy na pískovišti.

důležitost: 4

Napište funkci filtrujPodleRozsahuNaMístě(pole, a, b), která obdrží pole pole a odstraní z něj všechny hodnoty, které neleží mezi a a b. Test je: a ≤ pole[i] ≤ b.

Funkce by měla jen modifikovat pole. Neměla by nic vracet.

Příklad:

let pole = [5, 3, 8, 1];

filtrujPodleRozsahuNaMístě(pole, 1, 4); // odstraní čísla, která nejsou od 1 do 4

alert( pole ); // [3, 1]

Otevřít pískoviště s testy.

function filtrujPodleRozsahuNaMístě(pole, a, b) {

  for (let i = 0; i < pole.length; i++) {
    let hodnota = pole[i];

    // odstraní hodnotu, jestliže je mimo interval
    if (hodnota < a || hodnota > b) {
      pole.splice(i, 1);
      i--;
    }
  }

}

let testovacíPole = [5, 3, 8, 1];

filtrujPodleRozsahuNaMístě(testovacíPole, 1, 4); // odstraní čísla, která nejsou od 1 do 4

alert( testovacíPole ); // [3, 1]

Otevřít řešení s testy na pískovišti.

důležitost: 4
let pole = [5, 2, 1, -10, 8];

// ... váš kód je má seřadit v sestupném pořadí

alert( pole ); // 8, 5, 2, 1, -10
let pole = [5, 2, 1, -10, 8];

pole.sort((a, b) => b - a);

alert( pole );
důležitost: 5

Máme pole řetězců pole. Chtěli bychom mít jeho seřazenou kopii, ale pole chceme ponechat nezměněné.

Napište funkci zkopírujASeřaď(pole), která takovou kopii vrátí.

let pole = ["HTML", "JavaScript", "CSS"];

let seřazenéPole = zkopírujASeřaď(pole);

alert( seřazenéPole ); // CSS, HTML, JavaScript
alert( pole ); // HTML, JavaScript, CSS (beze změn)

Můžeme použít metodu slice(), aby vytvořila kopii, a spustit řazení na ní:

function zkopírujASeřaď(pole) {
  return pole.slice().sort();
}

let původníPole = ["HTML", "JavaScript", "CSS"];

let seřazenéPole = zkopírujASeřaď(původníPole);

alert( seřazenéPole );
alert( původníPole );
důležitost: 5

Vytvořte konstruktor Kalkulátor, který bude vytvářet „rozšiřitelné“ objekty kalkulátoru.

Úloha se skládá ze dvou částí.

  1. Nejprve implementujte metodu vypočítej(řetězec), která obdrží řetězec, např. "1 + 2", ve formátu „ČÍSLO operátor ČÍSLO“ (oddělené mezerou) a vrátí výsledek. Měla by rozumět plusu + a minusu -.

    Příklad použití:

    let kalkulátor = new Kalkulátor;
    
    alert( kalkulátor.vypočítej("3 + 7") ); // 10
  2. Pak přidejte metodu přidejMetodu(název, funkce), která naučí kalkulátor nové operaci. Obdrží operátor název a funkci o dvou argumentech funkce(a, b), která jej implementuje.

    Například přidáme násobení*, dělení / a umocňování **:

    let silnýKalkulátor = new Kalkulátor;
    silnýKalkulátor.přidejMetodu("*", (a, b) => a * b);
    silnýKalkulátor.přidejMetodu("/", (a, b) => a / b);
    silnýKalkulátor.přidejMetodu("**", (a, b) => a ** b);
    
    let výsledek = silnýKalkulátor.vypočítej("2 ** 3");
    alert( výsledek ); // 8
  • V této úloze nejsou závorky ani složité výrazy.
  • Čísla a operátor jsou oddělena právě jednou mezerou.
  • Pokud chcete přidat ošetřování chyb, můžete.

Otevřít pískoviště s testy.

  • Prosíme všimněte si, jak jsou metody uloženy. Jednoduše se přidávají do vlastnosti this.metody.
  • Všechny testy a číselné konverze se provádějí v metodě vypočítej. V budoucnu může být rozšířena, aby podporovala složitější výrazy.
function Kalkulátor() {

  this.metody = {
    "-": (a, b) => a - b,
    "+": (a, b) => a + b
  };

  this.vypočítej = function(řetězec) {

    let split = řetězec.split(' '),
      a = +split[0],
      op = split[1],
      b = +split[2];

    if (!this.metody[op] || isNaN(a) || isNaN(b)) {
      return NaN;
    }

    return this.metody[op](a, b);
  };

  this.přidejMetodu = function(jméno, funkce) {
    this.metody[jméno] = funkce;
  };
}

Otevřít řešení s testy na pískovišti.

důležitost: 5

Máte pole objektů uživatel, každý z nich má vlastnost uživatel.jméno. Napište kód, který je převede na pole jmen.

Příklad:

let jan = { jméno: "Jan", věk: 25 };
let petr = { jméno: "Petr", věk: 30 };
let marie = { jméno: "Marie", věk: 28 };

let uživatelé = [ jan, petr, marie ];

let jména = /* ... váš kód */

alert( jména ); // Jan, Petr, Marie
let jan = { jméno: "Jan", věk: 25 };
let petr = { jméno: "Petr", věk: 30 };
let marie = { jméno: "Marie", věk: 28 };

let uživatelé = [ jan, petr, marie ];

let jména = uživatelé.map(prvek => prvek.jméno);

alert( jména ); // Jan, Petr, Marie
důležitost: 5

Máme pole objektů uživatel, každý z nich má vlastnosti jméno, příjmení a id.

Napište kód, který z něj vytvoří jiné pole, které bude obsahovat objekty s vlastnostmi id a celéJméno, kde celéJméno se vygeneruje ze jméno a příjmení.

Příklad:

let jan = { jméno: "Jan", příjmení: "Novák", id: 1 };
let petr = { jméno: "Petr", příjmení: "Horák", id: 2 };
let marie = { jméno: "Marie", příjmení: "Králová", id: 3 };

let uživatelé = [ jan, petr, marie ];

let mapovaníUživatelé = /* ... váš kód ... */

/*
mapovaníUživatelé = [
  { celéJméno: "Jan Novák", id: 1 },
  { celéJméno: "Petr Horák", id: 2 },
  { celéJméno: "Marie Králová", id: 3 }
]
*/

alert( mapovaníUživatelé[0].id ) // 1
alert( mapovaníUživatelé[0].celéJméno ) // Jan Novák

Ve skutečnosti tedy potřebujete mapovat jedno pole objektů na druhé. Zkuste zde použít =>. Je tady malý chyták.

let jan = { jméno: "Jan", příjmení: "Novák", id: 1 };
let petr = { jméno: "Petr", příjmení: "Horák", id: 2 };
let marie = { jméno: "Marie", příjmení: "Králová", id: 3 };

let uživatelé = [ jan, petr, marie ];

let mapovaníUživatelé = uživatelé.map(uživatel => ({
  celéJméno: `${uživatel.jméno} ${uživatel.příjmení}`,
  id: uživatel.id
}));

/*
mapovaníUživatelé = [
  { celéJméno: "Jan Novák", id: 1 },
  { celéJméno: "Petr Horák", id: 2 },
  { celéJméno: "Marie Králová", id: 3 }
]
*/

alert( mapovaníUživatelé[0].id ); // 1
alert( mapovaníUživatelé[0].celéJméno ); // Jan Novák

Prosíme všimněte si, že v šipkové funkci musíme použít závorky navíc.

Nemůžeme ji napsat takto:

let mapovaníUživatelé = uživatelé.map(uživatel => {
  celéJméno: `${uživatel.jméno} ${uživatel.příjmení}`,
  id: uživatel.id
});

Jak si pamatujeme, existují dva druhy šipkových funkcí: bez těla hodnota => výraz a s tělem hodnota => {...}.

Zde JavaScript zachází s { jako se začátkem těla funkce, ne jako se začátkem objektu. Řešením je uzavřít objekt do „obyčejných“ závorek:

let mapovaníUživatelé = uživatelé.map(uživatel => ({
  celéJméno: `${uživatel.jméno} ${uživatel.příjmení}`,
  id: uživatel.id
}));

Nyní je to v pořádku.

důležitost: 5

Napište funkci seřaďPodleVěku(uživatelé), která obdrží pole objektů s vlastností věk a seřadí je podle této vlastnosti.

Příklad:

let jan = { jméno: "Jan", věk: 25 };
let petr = { jméno: "Petr", věk: 30 };
let marie = { jméno: "Marie", věk: 28 };

let pole = [ petr, jan, marie ];

seřaďPodleVěku(pole);

// nyní: [jan, marie, petr]
alert(pole[0].jméno); // Jan
alert(pole[1].jméno); // Marie
alert(pole[2].jméno); // Petr
function seřaďPodleVěku(pole) {
  pole.sort((a, b) => a.věk - b.věk);
}

let jan = { jméno: "Jan", věk: 25 };
let petr = { jméno: "Petr", věk: 30 };
let marie = { jméno: "Marie", věk: 28 };

let pole = [ petr, jan, marie ];

seřaďPodleVěku(pole);

// nyní seřazenéPole je: [jan, marie, petr]
alert(pole[0].jméno); // Jan
alert(pole[1].jméno); // Marie
alert(pole[2].jméno); // Petr
důležitost: 3

Napište funkci zamíchej(pole), která zamíchá (náhodně seřadí) prvky pole.

Vícenásobná volání zamíchej mohou vést k různým pořadím prvků. Například:

let pole = [1, 2, 3];

zamíchej(pole);
// pole = [3, 2, 1]

zamíchej(pole);
// pole = [2, 1, 3]

zamíchej(pole);
// pole = [3, 1, 2]
// ...

Všechna pořadí prvků by měla mít stejnou pravděpodobnost. Například [1,2,3] lze seřadit jako [1,2,3] nebo [1,3,2] nebo [3,1,2] atd., přičemž všechny možnosti mají mít stejnou pravděpodobnost.

Jednoduché řešení by mohlo být:

function zamíchej(pole) {
  pole.sort(() => Math.random() - 0.5);
}

let pole = [1, 2, 3];
zamíchej(pole);
alert(pole);

To jakžtakž funguje, protože Math.random() - 0.5 je náhodné číslo, které může být kladné nebo záporné, takže řadicí funkce přehází prvky náhodně.

Protože však řadicí funkce není určena k takovému použití, nebudou mít všechny permutace stejnou pravděpodobnost.

Například uvažujte níže uvedený kód. Spustí zamíchej 1 000 000krát a spočítá výskyty všech možných výsledků:

function zamíchej(pole) {
  pole.sort(() => Math.random() - 0.5);
}

// spočítáme výskyty všech možných permutací
let počty = {
  '123': 0,
  '132': 0,
  '213': 0,
  '231': 0,
  '321': 0,
  '312': 0
};

for (let i = 0; i < 1000000; i++) {
  let pole = [1, 2, 3];
  zamíchej(pole);
  počty[pole.join('')]++;
}

// zobrazíme počty všech možných permutací
for (let klíč in počty) {
  alert(`${klíč}: ${počty[klíč]}`);
}

Příklad výsledku (závisí na motoru JavaScriptu):

123: 250706
132: 124425
213: 249618
231: 124880
312: 125148
321: 125223

Jasně vidíme odchylku: 123 a 213 se objevují mnohem častěji než ostatní.

Výsledek kódu se může u různých JavaScriptových motorů lišit, ale už vidíme, že tento přístup je nespolehlivý.

Proč to nefunguje? Zhruba řečeno, sort je „černá skříňka“: vhodíme do ní pole a porovnávací funkci a očekáváme, že pole bude seřazeno. Kvůli naprosté náhodnosti řazení se však černá skříňka zblázní. To, jak přesně se zblázní, závisí na konkrétní implementaci, která se mezi jednotlivými motory liší.

Existují jiné dobré způsoby, jak tuto úlohu vyřešit. Například existuje skvělý algoritmus nazvaný Fisher-Yatesovo míchání. Myšlenkou je procházet pole v obráceném pořadí a vyměnit každý prvek s jiným prvkem před ním, náhodně vybraným:

function zamíchej(pole) {
  for (let i = pole.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1)); // náhodný index od 0 do i

    // vyměníme prvky pole[i] a pole[j]
    // dosáhneme toho použitím syntaxe „destrukturačního přiřazení“
    // podrobnosti o této syntaxi najdete v dalších kapitolách
    // totéž lze zapsat jako:
    // let t = pole[i]; pole[i] = pole[j]; pole[j] = t
    [pole[i], pole[j]] = [pole[j], pole[i]];
  }
}

Otestujme to stejným způsobem:

function zamíchej(pole) {
  for (let i = pole.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1));
    [pole[i], pole[j]] = [pole[j], pole[i]];
  }
}

// spočítáme výskyty všech možných permutací
let počty = {
  '123': 0,
  '132': 0,
  '213': 0,
  '231': 0,
  '321': 0,
  '312': 0
};

for (let i = 0; i < 1000000; i++) {
  let pole = [1, 2, 3];
  zamíchej(pole);
  počty[pole.join('')]++;
}

// zobrazíme počty všech možných permutací
for (let klíč in počty) {
  alert(`${klíč}: ${počty[klíč]}`);
}

Příklad výstupu:

123: 166693
132: 166647
213: 166628
231: 167517
312: 166199
321: 166316

Nyní to vypadá dobře: všechny permutace se objevují se stejnou pravděpodobností.

Navíc je Fisher-Yatesův algoritmus mnohem méně náročný na výkon, jelikož v něm není žádné „řazení“.

důležitost: 4

Napište funkci vraťPrůměrnýVěk(uživatelé), která obdrží pole objektů s vlastností věk a vrátí průměrný věk.

Vzorec pro výpočet průměru je (věk1 + věk2 + ... + věkN) / N.

Příklad:

let jan = { jméno: "Jan", věk: 25 };
let petr = { jméno: "Petr", věk: 30 };
let marie = { jméno: "Marie", věk: 29 };

let pole = [ jan, petr, marie ];

alert( vraťPrůměrnýVěk(pole) ); // (25 + 30 + 29) / 3 = 28
function vraťPrůměrnýVěk(uživatelé) {
  return uživatelé.reduce((předchozí, uživatel) => předchozí + uživatel.věk, 0) / uživatelé.length;
}

let jan = { jméno: "Jan", věk: 25 };
let petr = { jméno: "Petr", věk: 30 };
let marie = { jméno: "Marie", věk: 29 };

let pole = [ jan, petr, marie ];

alert( vraťPrůměrnýVěk(pole) ); // 28
důležitost: 4

Nechť pole je nějaké pole.

Vytvořte funkci unikát(pole), která vrátí pole obsahující všechny různé prvky pole.

Příklad:

function unikát(pole) {
  /* váš kód */
}

let řetězce = ["Haré", "Kršna", "Haré", "Kršna",
  "Kršna", "Kršna", "Haré", "Haré", ":-O"
];

alert( unikát(řetězce) ); // Haré, Kršna, :-O

Otevřít pískoviště s testy.

Projděme si prvky pole:

  • U každého prvku ověříme, zda výsledné pole již tento prvek obsahuje.
  • Pokud je tomu tak, budeme ho ignorovat, jinak ho přidáme do výsledku.
function unikát(pole) {
  let výsledek = [];

  for (let řetězec of pole) {
    if (!výsledek.includes(řetězec)) {
      výsledek.push(řetězec);
    }
  }

  return výsledek;
}

let řetězce = ["Haré", "Kršna", "Haré", "Kršna",
  "Kršna", "Kršna", "Haré", "Haré", ":-O"
];

alert( unikát(řetězce) ); // Haré, Kršna, :-O

Tento kód funguje, ale má potenciální problém s výkonem.

Metoda výsledek.includes(řetězec) vnitřně prochází pole výsledek a porovná každý jeho prvek s řetězec, aby našla shodu.

Jestliže tedy v poli výsledek je 100 prvků a žádný se nerovná řetězec, pak projde celé pole výsledek a provede právě 100 porovnání. A je-li výsledek velký, např. 10000, pak se vykoná 10000 porovnání.

To samo o sobě není problém, jelikož JavaScriptové motory jsou velmi rychlé, takže projít pole 10000 prvků je otázkou mikrosekund.

My však provádíme takový test pro každý prvek pole v cyklu for.

Jestliže tedy pole.length je 10000, budeme mít něco jako 10000*10000 = 100 miliónů porovnání. To je hodně.

Toto řešení je tedy dobré jen pro malá pole.

Později v kapitole Mapa a množina uvidíme, jak je optimalizovat.

Otevřít řešení s testy na pískovišti.

důležitost: 4

Řekněme, že jsme získali pole uživatelů ve formě {id:..., jméno:..., věk... }.

Vytvořte funkci seskupPodleId(pole), která z něj vytvoří objekt, jehož klíči budou hodnoty id a hodnotami prvky pole.

Příklad:

let uživatelé = [
  {id: 'jan', jméno: "Jan Novák", věk: 20},
  {id: 'anna', jméno: "Anna Nováková", věk: 24},
  {id: 'petr', jméno: "Petr Petřík", věk: 31},
];

let uživateléPodleId = seskupPodleId(uživatelé);

/*
// po volání bychom měli mít:

uživateléPodleId = {
  jan: {id: 'jan', jméno: "Jan Novák", věk: 20},
  anna: {id: 'anna', jméno: "Anna Nováková", věk: 24},
  petr: {id: 'petr', jméno: "Petr Petřík", věk: 31},
}
*/

Taková funkce se opravdu hodí, když pracujeme se serverovými daty.

V této úloze předpokládáme, že id je unikátní. V poli nesmějí být dva prvky se stejným id.

Prosíme použijte v řešení metodu pole .reduce.

Otevřít pískoviště s testy.

function seskupPodleId(pole) {
  return pole.reduce((obj, hodnota) => {
    obj[hodnota.id] = hodnota;
    return obj;
  }, {})
}

Otevřít řešení s testy na pískovišti.

Mapa tutoriálu

Komentáře

přečtěte si před komentováním…
  • Máte-li návrhy na zlepšení, vytvořte prosím issue na GitHubu nebo pull request místo komentáře.
  • Pokud v článku něčemu nerozumíte, napište prosím, čemu přesně a na kterém místě.
  • Pro vložení několika slov kódu použijte značku <code>, pro několik řádků je obalte značkou <pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)