webdevlpr

Zatvorenja su vazna jer kontrolisu sta jeste i nije u opsegu odredjene funkcije. Razumijevanje ovakvih procesa doprinosi razumijevanju JavaScripta i ovladavanju slozenijih scenarija.

Sta je rezultat koda ispod?

let animal = 'dog'; 

function printAnimal() {
console.log(animal); // pristupa vanjskoj varijabli
}

printAnimal(); // poziva funkciju

Funkcija printAnimal() radi sa varijablom animal koja je definisana izvan funkcije. Unutar funkcije vidimo instrukcije za ispisivanje sadrzaja te varijable u konzoli: dog.

Upravo smo vidjeli closure ili zatvorenje. U nekim programskim jezicima funkcija ne moze pristupiti varijablama koje su definisane izvan funkcije ili je funkciju neophodno napisati na poseban nacin da bi se to ostvarilo.

Java Script closures su funkcije koje pamte svoje vanjske varijable, tj. takve funkcije imaju referencu na svoje okruzenje u kom su kreirane. Drugim rijecima, zatvorenje funkciji daje pristup stanjima koje obuhvata vanjska funkcija.

Sa druge strane, ovako izgleda samostalna funkcija koja ~nije closure i koristi samo lokalne varijable, proslijedjene kroz argumente, iako tehnicki ona i dalje jeste closure. Zbunjujuce?

function f(a, b) {
return a / b;
}

f(6, 2); // 3

Zavisi od toga koga pitamo, neko ce se izjasniti da ovakvu funkciju smatra "uninteresting closure", neko drugi ce tvrditi da nije zatvorenje uopste jer ne koristi referencu na spoljni opseg (scope).

We don't normally call them closures unless they close over some other context and actually make use of the fact that they do, but at a technical level, they all are closures. stackoverflow: T.J. Crowder

Ugnijezdene funkcije ili nested functions

Obicno kada se misli na closures, one su u kontekstu ugnijezdenih funkcija. Funkcija je ugnijezdena kada se nalazi unutar druge funkcije sto se moze koristiti za organizovanje koda.

function sayHiBye(firstName, lastName) {

// pomocna ugnijezdena funkcija
function getFullName() {
let fullName = firstName + " " + lastName;
return fullName;
}

console.log( "Zdravo " + getFullName() );
// Zdravo Marko Doe
console.log( "Dovidjenja " + fullName );
// Error: Uncaught ReferenceError: fullName is not defined
}

sayHiBye('Marko', 'Doe');

Iako unutrasnje funkcije imaju referencu na vanjske, vanjska funkcija nema pristup stanju unutrasnje funkcije (osim onome sto funkcija proslijedi po dizajnu). Pristup je ogranicen izvan viticastih zagrada. To znaci da unutrasnja funkcija ima pristup varijablama firstName i lastName, ali vanjska funkcija nema pristup varijabli fullName. Ovakva enkapsulacija podataka moze sprijeciti curenje i izlaganje podataka tamo gdje nije potrebno.

Umjesto toga mozemo pozvati getFullName() u vanjskoj funkciji i vratiti fullName kao rezultat kroz kljucnu rijec return.

Closure funkcija demonstrira pristupacnost varijabli u opsegu odredjene funkcije.
Closure funkcija demonstrira pristupacnost varijabli u opsegu odredjene funkcije.

U JavaScriptu ono sto je unutra ima pristup onome sto je napolju. Kao kod jednosmjernog stakla, mozes vidjeti sta se nalazi napolju, ali ljudi napolju ne mogu vidjeti tebe.

Ono sto je takodje vrijedno pomena, ugnijezdenu funkciju varijabli mozemo dodijeliti i kao samu funkciju, sto kasnije mozemo koristiti negdje drugo. Bez obzira gdje bi je koristili, ona i dalje ima pristup istim vanjskim varijablama.

U sledecem primjeru varijabli dodjeljujemo sadrzaj ugnijezdene funkcije tako sto pozivamo vanjsku. Vanjska funkcija vraca unutrasnju kroz kljucnu rijeci return.

function vanjskaF(vanjskaV) {

function unutrasnjaF(unutrasnjaV) {
console.log('Vanjska varijabla: ' + vanjskaV);
console.log('Unutrasnja varijabla: ' + unutrasnjaV);
}

return unutrasnjaF;

// ili mozemo napisati
// return function unutrasnjaF(unutrasnjaV) {...}
}

let funkcija = vanjskaF('vanjska');

console.log(funkcija);

Sadrzaj funkcija varijable je sada:

function unutrasnjaF(unutrasnjaV) {
   console.log('Vanjska varijabla: ' + vanjskaV);
   console.log('Unutrasnja varijabla: ' + unutrasnjaV);
 }

Treba imati u vidu da ova vrijednost varijable takodje ostaje svjesna vanjskaV varijable vanjske funkcije ciju smo vrijednost proslijedili kroz argument.

Sada cemo pozvati ugnijezdenu funkciju sa argumentom. Naglasavam da — ugnijezdena funkcija pamti stanje vanjske funkcije i njene varijable bez obzira gdje je pozvana. To mozemo vidjeti u rezultatu koda ispod, gdje proslijedjen argument vanjskoj funkciji zadrzava vrijednost u oba primjera jer je unutrasnja funkcija pamti.

function vanjskaF(vanjskaV) {
let brojanje = 0;

return function unutrasnjaF(unutrasnjaV) {
console.log(++brojanje);
console.log('Vanjska varijabla: ' + vanjskaV);
console.log('Unutrasnja varijabla: ' + unutrasnjaV);
};
}

let funkcija = vanjskaF('vanjska');
funkcija('unutrasnja');
funkcija('druga unutrasnja');

U konzoli izgleda ovako:

1
Vanjska varijabla: vanjska
Unutrasnja varijabla: unutrasnja
2
Vanjska varijabla: vanjska
Unutrasnja varijabla: druga unutrasnja

Sta bi jos pisalo u konzoli kada bi ispod koda definisali novu varijablu?
let novaF = vanjskaF('nova');
novaF('unutrasnja nova');
Da li bi brojac ispisao 3 ili 1? Odgovor: Brojac bi se resetovao i prikazao 1.

U poslednjem primjeru se moze primjetiti da je ugnijezdena funkcija pamtila stanje varijable vanjske funkcije vanjskaV i brojanje koja se povecavala svakim pozivom ugnijezdene funkcije. Razlog je leksicko okruzenje koje se kreira na prvom pozivu vanjskaF(), ali da ne bi napravili prevelik korak, udubicemo se u tehnicke detalje koji omogucavaju funkcijama u JavaScriptu da pamte varijable i budu zatvorenja.
U clanku — Kontekst izvrsenja i leksicko okruzenje u JavaScriptu je objasnjeno kako takvi procesi rade u pozadini.

Closures se najcesce koriste da bi varijabli, koja je proslijedjena jednoj funkciji, kasnije mogli pristupiti u drugoj funkciji.