Iteracije kroz JavaScript nizove

Ovde opisani metodi spadaju u tzv. iteratore. To su metodi čija je uloga da "prođu" kroz sve elemente niza. Postoji nekoliko tipičnih "programerskih" potreba zbog kojih obično prolazimo kroz niz. To može biti pretraga, kreiranje novog niza na osnovu postojećeg, izračunavanje neke vrednosti (npr. zbir članova niza) ili provera ispravnosti elemenata niza.

Neki od tih uobičajenih zadataka su već rešeni za nas pomoću ovih metoda. Dakle JavaScript nam daje metod koji prolazi kroz niz sa određenom svrhom. Sve što mi treba da uradimo je da odredimo šta se konkretno dešava i to pomoću callback funkcije koja se prosleđuje metodu i zatim biva pozvana za svaki element niza.

Ako ste savladali lekciju o sortiranju niza, jasan vam je pojam callback funkcije. Da ponovimo - to je mogućnost da se određenoj funkciji prosledi neka druga funkcija, koja će kasnije biti pozvana jednom ili više puta. Zamislite tu glavnu funkciju kao "crnu kutiju" koja nešto radi, ali to što radi možemo da kontrolišemo pomoću "naše", callback funkcije.

Opšti iterator
a.forEach(F) Za svaki elemenat niza samo poziva callback funkciju F.
Iteratori koji generišu niz
a.filter(F) Vraća niz "ispravnih" elemenata. Za svaki elemenat poziva funkciju F koja vraća true ako je "ispravan".
a.map(F) Za svaki elemenat niza poziva funkciju F i vraća niz koji se sastoji iz vraćenih vrednosti funkcije F.
Agregirajući iteratori
a.every(F) Validira elemente niza i vraća true ako su svi "ispravni". Za svaki elemenat poziva funkciju F koja vraća true ako je "ispravan".
a.some(F) Validira elemente niza i vraća true ako je bar jedan "ispravan". Za svaki elemenat poziva funkciju F koja vraća true ako je "ispravan".
a.reduce(F) Vraća agregirajuću vrednost. Za svaki elemenat poziva funkciju F i prosleđuje joj ukupni dotadašnji rezultat i sam elemenat.
a.reduceRight(F) Isto kao reduce() s tim što prolazi kroz niz od poslednjeg ka prvom elementu.

Opšti iterator

Počećemo sa najjednostavnijim iteratorom, metodom forEach(). To je prosto metod koji prolazi kroz sve elemente niza i poziva callback funkciju. Za razliku od svih ostalih iteratora, ne vraća nikakvu vrednost, niti očekuje povratnu vrednost od callback funkcije.

Pošto ovde nigde nemamo povratne vrednosti, ako želimo da proizvedemo neki efekat, moramo da se oslonimo na uzgredne efekte funkcije (pozivi drugih funkcija, ispisi, rad sa DOM objektima, rad sa globalnim promenljivama...). Inače, sve drugo što važi za ovaj iterator, važi i za ostale. Svaki iterator se suštinski poziva na sledeći način:

niz.forEach(callback) niz.forEach(callback, this)

Prvi parametar je callback funkcija koja može biti samo referenca na funkciju, odnosno naziv funkcije, ili čak i cela anonimna funkcija. To je funkcija koja će se pozivati za svaki član niza. Parametar this nije obavezan. On predstavlja objekat koji će callback funkciji biti prosleđen kao objekat this.

Callback funkciji se prosleđuju tri parametra. Naravno, sećate se da JavaScript dozvoljava da ne prihvatite neki parametar, pa tako ova funkcija može imati bilo koji od sledećih oblika:

function(element) function(element, indeks) function(element, indeks, niz)

Parametar element predstavlja sam element koji se trenutno "obrađuje". U većini slučajeva nam je to dovoljno. Drugi parametar - indeks, predstavlja indeks tog elementa u nizu, a parametar niz predstavlja referencu na sam niz.

Još jedna bitna stvar: iteracije se ne mogu nasilno zaustaviti. Kada koristimo bilo kakvu cikličnu strukturu u JavaScriptu (for, while ili do-while), u svakom trenutku možemo da iskočimo iz ciklusa, korišćenjem komande break. Metodi iteratori nam ne dozvoljavaju takvu mogućnost. Ceo niz mora da se "izvrti".

Zaista, ovo nam baš ne odgovara kod sekvencijalne pretrage - to je ispitivanje jednog po jednog elementa dok se ne pronađe odgovarajući. U takvim situacijama nema potrebe da se ispituju elementi ostatka niza (kojih može da ima baš puno) i programeri tada "iskaču" iz ciklusa korišćenjem komande break. Srećom, iteratori every() i some() mogu da nam pruže i tu funkcionalnost, a malo dalje u tekstu ćemo videti i kako.

Primer

Promena niza unutar callback funkcije


  var a = [56, 23, 87, 11, 44];
    
  a.forEach(function(val, ind, niz) {
    if (ind == 0) {
      niz.shift();
      niz.shift();
    }
    console.log(ind + ": " + val);
  });

  // tokom iteracija dobijamo vrednosti:
  // 0: 56
  // 1: 11
  // 2: 44

  console.log(a);  // niz je: [87, 11, 44]

Pogledajte šta se dešava u ovom primeru. Na samom početku, u prvoj iteraciji (kada je ind jednak 0), skidamo prva dva elementa iz niza. Dakle, uklanjamo trenutni element (a[0]=56) i prvi posle njega (a[1]=23). U nizu ostaju samo elementi [87, 11, 44].

E, sada, očekujemo da sledeći elemenat koji se obrađuje bude 87, međutim, on će biti preskočen. Jednostavno, već u drugoj iteraciji, njegov indeks je 0, a brojač se povećao na 1. Tako da će u drugoj iteraciji biti obrađivan element 11.

Iteratori koji kreiraju novi niz

Ova dva iteratora služe da kreiramo novi niz na osnovu postojećeg. Metod map() će formirati novi niz od rezultata callback funkcije.

[a1,a2,a3,a4,a5].map() --> [r1,r2,r3,r4,r5]

Sa druge strane, metod filter() formira novi niz koji sadrži elemente postojećg niza, za koje je rezultat callback funkcije bio true.

[a1,a2,a3,a4,a5].filter() --> [a1,a3,a4]

Primer

Izbacivanje nedefinisanih elemenata

Znamo da kada u niz ubacujemo elemente sa sporadičnim indeksima, da će postojati i svi elementi između njih, ali će biti nedefinisani. Evo zgodnog načina da se otarasimo tih nedefinisanih elemenata. Korstimo metod filter(), dok u callback funkciji vraćamo vrednost true ako element nije nedefinisan, false u suprotnom (ako jeste).


  var a = [];
  a[3] = "Pera";
  a[8] = "Mika";
  a[10] = "Žika";
    
  console.log(a);  // [-,-,-, "Pera", -,-,-,-, "Mika", -, "Žika"]

  a = a.filter(function(val) {
    return val != undefined;    // vraća true ako nije nedefinisan
  });

  console.log(a);  // ["Pera", "Mika", "Žika"]

Pošto iteratori nisu mutatori (tj. ne menjaju originalni niz), rezultat poziv ovog metoda će biti novi niz. Da bismo promenili sam niz a, moramo da bukvalno taj novi niz "ubacimo" u referencu a (a = a.filter(...)).

Ocene studenata

Metodom map() kreiramo niz sasvim novih elemenata, gde svaki novi element odgovara jednom elementu iz originalnog niza. Na osnovu niza poena na ispitu, formiraćemo niz ocena studenata.


  var a = [55, 23, 45, 80, 34, 92, 76];

  var ocene = a.map(function(val) {
    var x = Math.round(val / 10);
    return x < 6 ? 5 : x;
  });

  console.log(ocene);  // [6, 5, 5, 8, 5, 9, 8]

Prilično je jednostavno - na osnovu poena računamo ocenu i vraćamo je kao rezultat callback funkcije. Tako se formira novi niz.

Agregirajući iteratori

Agregirajući iteratori prolaze kroz niz sa ciljem da ga svedu na jednu vrednost. Najpre ćemo obraditi dva logička agregirajuća iteratora - što u prevodu znači, metoda koji služe da "proverimo ispravnost" članova niza.

Metod every() će vratiti vrednost true ako svaki element niza zadovoljava uslov.

[a1,a2,a3,a4,a5].every() --> true/false

Metod some() će vratiti vrednost true ako makar jedan element niza zadovoljava uslov.

[a1,a2,a3,a4,a5].some() --> true/false

A kako metod "zna" da li neki član niza zadovoljava uslov? Pa, ne zna - zato za svaki element niza poziva našu callback funkciju. Ova funkcija, za zadatu vrednost, mora da vrati true ili false (tj. vrednosti koje se tako tumače).

Primer

Da li su svi položili?

Niz pnt sadrži brojeve poena studenata. Pitanje "da li su svi položili", je u stvari - "da li su svi ostvarili više od 54 poena". Ovo je idelana situacija za funkciju every().


  var pnt = [55, 32, 94, 20, 75];
  
  var svi = pnt.every(function(elem) {
    return elem > 54;
  });

U promenljivoj svi se nalazi odgovor na pitanje da li su svi studenti položili ispit (naravno da nisu).

Da li ima bar neko sa 100 poena?

Kad već nisu svi položili, hajde da vidimo da li ima bar nekog sa svih 100 poena. Dakle, svaki broj poena se upoređuje sa 100, i ako se nađe bar jedan takav, vrednost će biti true.


  var pnt = [55, 32, 94, 20, 75];
  
  var sto = pnt.some(function(elem) {
    return elem == 100;
  });

Kao što nam je očigledno iz samih poena, ni ovde se studeti nisu proslavili.

Prekid iteracija

Recimo da hoćemo da prođemo kroz niz u potrazi za prvim elementom većim od 100. Bez korišćenja iteratora, radili bismo nešto slično ovome:


  var a = [56, 23, 120, 87, 115, 44];
  
  var pronadjen = -1; 
  for (var i=0; i<a.length; i++) {
    if (a[i] > 100) {
      pronadjen = i;
      break;
    }
  }
  console.log(a.pronadjen);

Čim naletimo na prvi element veći od 100, odmah se iskače iz cklusa. Već smo rekli da iteratori ne dozvoljavaju iskakanje, ali su zato some() i every() tako optimizovani, da sami "iskaču", čim callback funkcija vrati odgovarajuća vrednost.


  var a = [56, 23, 120, 87, 115, 44];
  
  var pronadjen = -1;
  a.some(function(val, ind) {
    if (val > 100) {
      pronadjen = ind;
      return true;
    }
    return false;
  });
  console.log(pronadjen);

Biće obrađeni samo elementi 56, 23 i 120. Zašto? Pa, funkcija je tada vratlia vrednost true. Iterator some() je "zadovoljen" ako bar jedan član niza zadovoljava uslov, što znači da nema svrhe ispitivati otale elemente niza.

Poslednja dva metoda su malo "opštija" i mnogo moćnija od odstalih. U stvari, pomoću metoda reduce() možemo sve što i pomoću svih do sada opisanih metoda (ok, osim da "iskočimo" iz iteracija - to je ipak specijalnost every() i some() metoda). Ovaj metod služi da svede niz na jednu (ali pazite - bilo kakvu) vrednost.

[a1,a2,a3,a4,a5].reduce() --> X

Glavna snaga metoda reduce() je što callback funkciji ne prosleđuje samo pojedinačni element niza, već i dotadašnji rezultat. Funkcija onda na osnovu tog elementa "transformiše" rezultat, i vraća ga nazad. Ukupna vrednost metoda će u stvari biti poslednja vraćena vrednost callback funkcije.

Metod reduce() je moguće pozvati sa jednim ili dva parametra. Prvi je uvek callback funkcija, a drugi je početna vrednost rezultata. To je "prethodna" vrednost koja se prosleđuje pri prvom pozivu callback funkcije. Ako se ne zada, prosleđuje se 0.

a.reduce(callback) a.reduce(callback, početna_vrednost)

Callback funkcija se poziva sa sledećim parametrima:

function(vrednost, element, indeks, niz)

Parametri element, indeks i niz su nam već poznati iz svih prethodno opisanih metoda. Parametar vrednost predstavlja dotadašnji rezultat (ili početni rezultat za prvi element niza).

Metod reduceRight() radi isto što i reduce(), samo prolazi kroz niz u obrnutom redosledu - od poslednjeg do prvog elementa.

[a1,a2,a3,a4,a5].reduceRight() --> X

Primer

Zbir elemenata niza

Da počnemo sa najjednostavnijim... Računamo zbir elemenata niza.


  var a = [5, 2, 5, 4, 9];
    
  var zbir = a.reduce(function(rez, val) {
    return rez + val;
  });

  console.log(zbir);  // zbir = 25

Promenljiva rez po dafaultu počinje od 0. Sa svakim pozivom, predstavljaće prethodni rezultat (dotadašnji zbir). Prosleđuje se funkciji, zajedno sa elementom, funkcija ih sabira i vraća kao novi rezultat koji će se proslediti u sledećem pozivu, sve dok ne postane krajnji rezultat.

Obrtanje elemenata niza

Ako želimo da napravimo novi niz, koji se sastoji iz elemenata originalnog niza u obrnutom redosledu, možemo koristiti metod reduceRight(). Naravno, mogli smo da koristimo i metod reduce() u kombinaciji sa unshift().


  var a = [4, 8, 15, 16, 23, 42];
  
  var b = a.reduceRight(function(niz, elem) {
    niz.push(elem);
    return niz;
  }, []);

  console.log(b);  // [42, 23, 16, 15, 8, 4]

Poenta je da vidite kako se kao rezultat funkcije može prosleđivati i niz. Primetite kako smo kao početnu vrednost zadali prazan niz []. Onda u svakoj iteraciji dodajemo po jedan element. Zahvaljujući tome ovi metodi mogu da zamene i filter() i map() metode.

Simuliranje metoda every()

Hajde da vidimo kako bismo ispitali pomoću metoda reduce() da li su svi elementi veći od 10.


  var a = [15, 16, 23, 8, 42];
  
  var veci = a.reduce(function(rez, elem) {
    return rez && (elem > 10);
  }, true);

  console.log(veci);  // false

Sada radimo sa logičkom vrednosšću. Ona počinje sa true i AND-uje se sa rezultatom upoređivanja elemenat i broja 10 (koji može biti true ili false). Čim se dogodi da neki element bude manji od 10, ova vrednost "sklizne" u false, i nikako više ne može da se podigne na true.

U ovakvom slučaju bismo ipak koristili metod every() zbog efikasnosti, pošto se zaustavlja posle prvog false rezultata.

  1. J. Resig, B. Bibeault (2013): Secrets of the JavaScript Ninja, Manning Publications, New York
  2. A. Rauschmayer (2014): Speaking JavaScript, O’Reilly, Sebastopol
Svi elementi sajta Web'n'Study, osim onih za koje je navedeno da su u javnom vlasništvu, vlasništvo su autora i ne smeju se koristiti, u celosti ili delimično bez pismenog odobrenja autora. To uključuje tekstove, slike, ilustracije, animacije, prateći grafički materijal i programski kod.
Ovaj sajt koristi tehnologiju kolačića (cookies). Detaljnije o tome možete pročitati u tekstu o našoj politici privatnosti.