Přejít k navigační liště

Zdroják » JavaScript » JavaScript Restart – Hurá na pole

JavaScript Restart – Hurá na pole

Články JavaScript

Velkou bolestí JavaScriptu bývalo poměrně málo nativních funkcí pro práci s poli v JavaScriptu, a klávesy “F”, “O” a “R” jsou na klaviaturách našich živitelů značně osahané. Pole je, vzhledem k chudému počtu (2 v ES4) typů použitelných pro vytváření datových struktur, druhým nejužívanějším.

Nálepky:

Array.forEach

První (a vlastně nejméně zajímavou funkcí) je forEach, a to zejména proto, že nám umožní pouze nahradit “for” cyklus. Takže místo obligátního:

for (var i = 0; i < items.length; i++) {
    item = items[0];
    //kód
}

můžeme použít toto:

items.forEach(function(item, i) {
    //kód
});

Zatím žádný zázrak a navíc malý kámen úrazu, a tím je kontext callback funkce, čili je si třeba ohlídat, aby reference this znamenala to, co chceme my (protože jinak je kontext window). To se dá zařídit druhým argumentem.

items.forEach(function(item, i) {
     //kód
}, context);

Array.forEach je ekvivalentem $().each(), ovšem proč standardizátoři Javascriptu nepoužili kratší each, je pro mě záhadou. Možná protože je tu přeci jen jeden rozdíl, a to v počtu parametrů callback funkce. Obsahuje totiž:

  1. aktuální položku pole
  2. index
  3. referenci na procházené pole

jQuery v $().each() předává callbacku pouze první dva parametry. Druhým rozdílem je, že vrací vždy undefined na rozdíl od $().each(), která vždy vrací pole.

Malý příklad použití v praxi – dejme tomu, že chceme pro každý button zaregistrovat stejný handler. Pak kód v jQuery bude vypadat takto:

$('button').click(handler);

a čistém JavaScriptu takto:

document.querySelectorAll('button').forEach(function(element) {
    element.addEventListener('click', handler);
});

Uznávám, že proti jQuery je toto poněkud ukecané.

Nemohu se na tomto místě nezmínit o sadistických sklonech některých vývojářů, kteří vymýšlejí vtipné mozkolamy a variace na for cyklus, jako třeba:

for (var i = 0; i < items.length; ++i) {
    //kód
}

Čtení takového kódu bolí, a chudák dědic jejich kódu ještě večer usíná s otázkou: “Proboha proč?”

Array.map

Šikovnou funkcí je map, která je právě hojně využívaná v jQuery, zřejmě proto, že se s ní dá nadělat spousta parády.

Array.map na rozdíl od Array.forEach už cosi vrací, a to pole s hodnotami, které vrátíme v callback funkci.

Následující příklad neudělá nic lepšího než kopii pole:

var newItems = items.map(function(item) {
    return item;
}, context);

Pojďme se tedy podívat na něco užitečnějšího. Máme pole id elementů a chceme na základě tohoto pole dostat přímo pole elementů.

var ids= [
    '#name',
    '#lastname',
    '#age'
];

var els = ids.map(function(id) {
    return document.querySelectAll(id);
});

Array.filter

Array.filter je funkce na první pohled podobná Array.map s tím rozdílem, že vrací položky pole, pro které vyhověla podmínka v callbacku.

Následující kód vrátí nová pole, která nebudou obsahovat undefined hodnoty z pole items:

var newItems = items.filter(function(item) {
    return item !== undefined;
}, context);

Následující příklad slouží k odstranění duplicitních hodnot z pole (nechal jsem se inspirovat tímto příkladem):

var uniqueArray = duplicates.filter(function(elem, pos, duplicatesArray) {
    return duplicatesArray.indexOf(elem) == pos;
});

Šikovné, že?
Další užitečné využití funkce Array.filter je při hledání v poli objektů. Chceme-li například dostat seznam všech objektů, které jsou pouze pro čtení, provedeme to takhle:

var objectList = [
    {readonly: false},
    {readonly: true},
    {readonly: true}
];

var readonlyItems = objectList.filter(function(item) {
    return item.readonly;
});

Array.reduce

Array.reduce je prazvláštní funkce, jejíž dokumentaci jsem musel přečíst několikrát, než jsem pochopil, co vlastně dělá. Taktéž je zajímavá tím, že momentálně není implementována v jQuery (na rozdíl od filter a map).

Její callback funkce dostává tyto parametry:

  1. výsledek vrácený při předchozím spuštění callback funkce
  2. aktuální položku pole
  3. index
  4. procházené pole

a právě v první položce je kámen úrazu, jelikož na první pohled to vypadá, že vrací předchozí prvek pole, ale není tomu tak.
Vypadá to nějak takhle:

var newItems = items.reduce(function(previousItem, currentItem) {
    //kód
});

Na co tohle použít? Kupříkladu jsem již několikrát hledal elegantní způsob, jak sečíst všechny prvky v poli. S Array.reduce je to hračka:

var sum = itemToSum.reduce(function(previous, current) {
  return previous + current;
});

Zde je na místě otázka, jaká hodnota je v prvním parametru previous při prvním průchodu. Je to hodnota první položky, kterou však lze přednastavit jako parametr funkce Array.reduce.

var sum = [1,2,3].reduce(function(previous, current) {
  return previous + current;
}, 10);

Výsledek bude tudíž číslo 16.

Array.every

Další z fukcí pole, která vrací jednu hodnotu, je Array.every. Ta nad každou položkou pole provede test, a budou-li všechny vracet true, pak iArray.every vrací true.

Budeme-li chtít vědět, zda jsou všechny objekty (z výše uvedeného příkladu) pouze ke čtení, můžeme použít toto:

var objectList = [
    {readonly: true},
    {readonly: false},
    {readonly: true}
];

var readonly = objectList.every(function(item) {
    return item.readonly;
});

Při použítí této funkce je třeba myslet na to, že pokud jednou dostane false, už se dále nepokračuje, čili v tomto příkladě se třetí prvek pole netestuje.

Array.some

A když už máme funkci pro test na všechny položky pole, tak ještě přidáme test pro alespoň jednu. Je to tak, Array.some vrací true, pokud alespoň jeden test vrátí true.

var readonly = [
    {readonly: false},
    {readonly: true},
    {readonly: true}
].every(function(item) {
    return item.readonly;
});

A opět je třeba nezapomenout, že cyklus skončí ve chvíli, kdy bude rozhodnuto, tudíž na třetí položku opět nedojde.

Všechny výše zmíněné funkce jsou implementovány v Internet Exploreru 9+, tudíž, pokud nás IE8 netrápí, můžeme je zvesela používat.

Perlička na závěr

K povídání o “polních” funkcích v JavaScriptu přihodím malý trik. Často se stane, že je třeba ověřit funkce validátoru, konkrétně validaci délky, jenže kde honem vzít řetězec např. o délce 255 znaků.

Řešení vypadá takto:

Array(256).join('a');

Proč 256, proč ne 255? Pojďme se podívat na to, co taková konstrukce vlastně dělá.

Array(256);

Vytvoří pole o 256 prvcích undefined. Následně metoda join mezi tyto prvky vloží 255 krát “a”. A máme hotovo.

Další možné řešení je pomocí funkce Array.fill:

Array(255).fill('a').join('');

anebo ještě lépe pomocí String.repeat, což je přesně to, co hledáme:

'a'.repeat(255)

Jenže tyto funkce jsou součástí ES6 a momentálně si je můžete vyzkoušet pouze ve Firefoxu nebo z polyfillem.

Komentáře

Subscribe
Upozornit na
guest
28 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Paladin

Co je spatne na:

for (var i = 0; i < length.items; ++i) {
//kód
}

?

pavel

Vubec nic. Nicmene casto potrebuji iterovat pres prvky pole, ale index me nezajima.

Stanislav Nechutný

Třeba to, že by to mělo být items.length místo length.items

Paladin

Vzhledem k tomu, ze tak to bylo i v tom uplne prvnim kodu, tak jsem to povazoval proste za preklep a nevydel za nutne do toho ryt.

Dave

jo, je videt ze si vazne programatorskej buh, dokazal si pochopit i tak slozitou vec, jako je operator precedence …

Peter

Spatne na tom kode je, ze v kazdej iteracii sa zistuje velkost pola co zbytocne spomaluje slucku. ;-)

alesroubicek

Nejboj se, že si to JIT nesrovná.

Peter

Pravdu mas predsedo. Dnes uz si vlastne runtime .length cachuje. Teraz som si vsimol, ++i. Asi uz starnem.

Dash

To snad ne…

Martin

Hned na první pohled musí byt jasné že,

document.querySelectorAll(‘button’).forEach

nebude fungovat a to nejenom kvůli tem uvozovkám, ale hlavně proto že, querySelectorAll nevrací pole, ale NodeList.

Ani slovo o tom ze for je lehce rychlejší než forEach, nebo co je důležitější, že for můžu přerušit kdežto forEach jak už název napovídá je pro všechny prvky.

DavidGrudl

$.each lze přerušit btw taky. Přerušitelný forEach() je vlastně every().

karel

Takže podle tebe je lepší miliarada callbaku než for cyklus, no poťež koště, víc takových jako ty a už abych si obědnal další ram a pár dalších jader

Jinaq

No, každé má sve kouzlo. Minimálně se může hodit nový scope funkce, například.

ivoszz

I když zápis je formou callbacku, o žádný callback se nejedná, jde o synchronní kód, takže tvá obava je lichá. :)
BTW, JS jede v jednom vlákně, takže to cos napsal je samozřejmě úplný blábol.

peci1

Funkce reduce neni zadny prapodivny vymysl. Je to jeden ze zakladnich stavebnich kamenu funkcionalniho programovani. To jen, aby si nekdo nemyslel, co to ti standardizatori pridali do Javascriptu za „blbinu“. Naopak – priblizuji ho vic k ciste funkcionalnim jazykum.

tomas.pavlacky

Raději bych použil addEventListener.

[attachEvent is no longer supported. Starting with Internet Explorer 11, use addEventListener. For info, see Compatibility changes.]

martin

Na tyto věci je tu knihovna underscore.js.

Honza

Nebo lodash…

alesroubicek

Nebo Transducers-js, které můžete použít rovnou s jakýmkoliv Array-like kolekcí případně s kolekcí implementujícím ES6 @@iterator. A výhodou je mnohem menší paměťová náročnost a mnohem rychlejší zpracování.

Stejné benefity získáte i immutabilních kolekcí, které posktuje např. knihovna Immutable.js.

A když se naučíte používat transducery nad poli, můžete pak rovnou začít pracovat i s event streamy, například pomocí knihovny Kefir.js, která má podpru pro transducery vestavěnou.

martin

K čemu je to dobré? Oproti underscore/lodash to vypadá o 2 levely složitější.

mkoubik
  1. stejná operace se dá provádět na libovolné „kolekci“ aniž by vás při implementaci zajímalo co to bude
  2. pokud děláš nšco složitějšího (typu array.map(…).filter(…).reduce(…)) tak se celá opearce provede najednou pro každý prvek a nemusí se zbytečně vytvářet dvě pomocné mezikolekce
mkoubik

2 b. můžeš to tudíž používat na lazy kolekcích (z „pole“ všech přirozených čísel uděláš „pole“ všech prvočísel) aniž by se materializovaly

Snehuliak

Dobry clanok ale privital by som keby bolo zmienene of ktorejze verzie JS su ktore funkcie podporovane a tiez ci su nejake nekompatibility v browseroch (vratane mobile). Popripade odkaz na shim…

Dakujem.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.