JavaScript anonimne funkcije
Najjednostavnije rečeno, anonimna funkcija je ona koja nema naziv.
Ove teme smo se već dotakli u tekstu vezanom za reference na funkcije. Tamo smo pokazali kako je moguće napraviti referencu na funkciju, pošto je i sama funkcija u stvari objekat. Praktično, referenca je promenljiva koja "pokazuje" na objekat (funkciju) negde u memoriji. Između ostalog imali smo slučaj reference na funkciju koja nije imala ime - anonimnu funkciju.
Početnici često poistovećuju sledeće dve stvari:
function fun() {...}
var fun = function() {...}
Prvo je "klasična" deklaracija imenovane funkcije fun(). Drugo je referenca na anonimnu funkciju, uvedenu preko funkcijskog izraza. U programu bismo i jednu i drugu funkciju pozivali na isti način:
fun()
Dva navedena načina uvođenja funkcije u program su veoma slična, ali nisu ista. Naša preporuka je da ako imate izbora, uvek koristite klasičnu deklaraciju, tj. prvi način. Za to postoji jedan jako dobar razlog, a to je hoisting.
Drugim rečima, funkciju koja je deklarisana na klasičan način će biti moguće pozvati kroz celu oblast važenja, bez obzira da li je deklarisana pre ili posle poziva. Funkcije uvedene na drugi način, preko funkcijskog izraza, možemo pozvati isključivo posle njihove deklaracije.
Upotreba anonimnih funkcija
Da li onda anonimne funkcije nemaju svrhu? Naprotiv. Ako vam "leži" funkcionalna paradigma programiranja, anonimne funkcije će vam se sigurno dopasti, pošto JavaScript podržava lambda izraze, odnosno mogućnost da funkcije (višeg reda) primaju druge funkcije kao parametre, kao i da vraćaju funkcije kao rezultate.
Zbog asinhrone prirode weba i načina na koji pravimo web aplikacije, vrlo je moguće da ćemo koristiti anonimne funkcije u obliku callback funkcija. Drugim rečima, pošto će naš program vrlo verovatno morati da čeka odgovor servera na neki upit, moramo da deklarišemo funkciju koja se poziva kada se to desi.
Najzad, uvođenje anonimnih funkcija preko funkcijskog izraza je jedan od načina za kreiranje metoda objekta. Tada, hteli-ne hteli, moramo da napravimo svojstvo objekta koje je referenca na funkciju.
Naravno, u svakom od ovih slučajeva, umesto anonimne, možemo koristiti imenovanu funkciju u funkcijskom izrazu, što nam je neophodno ako je funkcija rekurzivna (funkcija koja poziva samu sebe).
Samopozivajuće funkcije
Samopozivajuće funkcije se izvršavaju odmah po deklaraciji, bez potrebe da se eksplicitno pozovu. Ove funkcije se zadaju kao funkcijski izrazi unutar zagrade, za kojima se odmah navodi lista parametara u zagradama. Ove funkcije su najčešće anonimne.
(function(formalni parametri) {...})(stvarni parametri)
ili
(function(formalni parametri) {...}(stvarni parametri))
Samopozivajuće funkcije koristimo ako nam zatreba npr. "instant" oblast važenja ili closure funkcija.
Ove funkcije mogu biti čak i imenovane za potrebe rekurzije, ali zapamtite da se van "same sebe" ne mogu pozivati. Samopozivajuće funkcije su "jednokratne".
Primeri upotrebe anonimnih funkcija
Referenca na funkciju
var obj = {x:150, y:120};
obj.kretanje = function(dx, dy) {
this.x += dx;
this.y += dy;
};
obj.kretanje(10, 5);
Ovo je relativno jednostavna primer, gde smo definisali objekat obj sa svojstvima x i y (recimo da su to koordinate). Takođe smo definisali i dodatno svojstvo obj.kretanje kao referencu na anonimnu funkciju koja prima dva parametra dx i dy, koja onda sabira sa svojstvima objekta. Time ova referenca efektivno postaje metod objekta.
Callback funkcija
window.setTimeout(function() {
location.href = "http://webnstudy.com";
}, 5000);
niz.sort(function(a,b) {
return b-a;
});
Prvi primer demonstrira upotrebu anonimne callback funkcije prilikom postavljanja tajmera. Zadali smo da se posle 5 sekundi izvrši anonimna funkcija koja zadaje web čitaču novu lokaciju. Dakle, kada se poziv metodu setTimeout izvrši, neće se ništa desiti, ali kada se "odbroji" 5 sekundi (5000 milisekundi), poziva se zadata callback funkcija.
Drugi primer nam je poznat iz teksta u kome smo objašnjavali sortiranje niza. Suštiniski, algoritam prolazi kroz elemente niza, a za svako upoređivanje poziva anonimnu callback funkciju koju smo zadali, i koja "odlučuje" da li elemente a i b treba zameniti.
Lambda izrazi
function ispis(fun) {
return function() {
var r = fun.apply(null, arguments);
console.log(r);
return r;
};
}
var calc = ispis(function(a,b) {
return a+b;
});
var x = calc(5, 10) * 2; // ispis: 15 (ispisuje rezultat )
console.log(x);
Ok, ovde smo namerno malo preterali da bismo demonstrirali kako možemo da prosleđujemo i dobijamo kao rezultat anonimnu funkciju.
Funkcija ispis() kao parametar uzima neku funkciju, a kao rezultat vraća novu funkciju. Ova nova funkcija je anonimna. Inače nova funkcija "omotava" prosleđenu funkciju i služi da za bilo koju zadatu funkciju ispiše njen rezultat u konzoli. Najpre se prosleđena funkcija pozove da bi se njen rezultat zabeležio, onda se taj rezultat ispiše, a na kraju se rezultat vrati, kao da je prosleđena funkcija bila originalno pozvana. Pošto ne znamo koliko će parametara imati prosleđena funkcija, mormao da je pozovemo preko metoda apply() koji kao parametre prosleđuje niz, a ne pojedinačne parametre. Više o ovom metodu pročitajte u tekstu o parametrima funkcije.
Dalje u našem primeru definišemo calc kao referencu na funkciju koju će nam vratiti funkcija ispis(). Ovde opet koristimo anonimnu funkciju ali kao parametar sa kojim se poziva funkcija ispis(). Ova anonimna funkcija prima dva parametra a i b, i kao rezultat vraća njihov zbir.
Na kraju konačno testiramo da li sve radi. Računamo x tako što pozovemo funkciju preko reference calc() kojoj smo zadali dva broja. Funkcija calc() je u stvari funkcija koju smo "vratili" i kao rezultat će dati zbir dva prosleđena broja (to je anonimna funkcija koju smo prosledili), ali takođe i ispisuje taj zbir (vraćena funkcija koja je omotala prosleđenu funkciju).
Samopozivajuće funkcije
(function(x,y) {
var blok = document.createElement("div");
blok.innerHTML = "Novi DIV blok";
blok.onclick = function() {
blok.style.width = x + "px";
blok.style.height = y + "px";
};
document.body.appendChild(blok);
})(250, 100);
U gornjem primeru smo napravili samopozivajuću anonimnu funkciju koja se izvršava odmah i prima dva parametra (250 kao x i 100 kao y). Funkcija kreira DIV elemenat na koji pokazuje referenca blok. Definišemo sadržaj bloka i na kraju ga dodajemo na kraj dokumenta (body).
Kao bonus, definisali smo i anonimnu funkciju kao event handler, koja se izvršava kada se klikne na blok koji smo napravili.
Ostali smo dužni da pokažemo kako nas samopozivajuća funkcija spašava od closure zamke. Sećate se ovog primera? Problem je bio kako sačuvati vrednosti promenljivih iz nadfunkcije u trenutku kreiranja closure funkcije.
Ideja je da "umotamo" kreiranje closure-a u samopozivajuću funkciju:
function napravi(N)
{
var blok;
for (var i=1; i<=N; i++) {
blok = document.createElement("div");
blok.innerHTML = "Klikni me.";
(function() { // anonimna samopozivajuća funkcija
var rbr = i; // lokalno pamtimo vrednost od i
blok.onclick = function() // i tu praviomo event handler
{
this.innerHTML = "Ovo je BLOK " + rbr; // ovo funkcioniše
};
}());
document.body.appendChild(blok);
}
}
//...
napravi(10);
Jeste li shvatili šta se dešava? Samopozivajuća funkcija je takođe closure. Dakle, kada kreiramo DIV blok, odmah se izvršava ova funkcija - ona ima pristup "nadpromenljivoj" i i beleži je u svoju lokalnu promenljivu rbr.
Onda unutar nje kreiramo event handler funkciju, koja je closure od closure-a, što je sasvim regularno. Promenljiva rbr će uvek imati onu vrednost promenljive i koaj je bila u trenutku kreiranja.
- J. Resig, B. Bibeault (2013): Secrets of the JavaScript Ninja, Manning Publications, New York
- A. Rauschmayer (2014): Speaking JavaScript, O’Reilly, Sebastopol