Sta je closure u racunarskom programiranju
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.
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
.
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.