Closure
Vizsgáljuk meg a következő kódot.
var globalVariable = 'global';
function A(){
var a1 = 'local variable inside function A';
function B(){
var b1 = 'local variable inside function B';
alert(globalVariable +"\n"+ a1);
}
return B;
}
var fncB = A();
fncB();
// A()(); // a fenti 2 sorral ekvivalens
Két egymásba ágyazott függvényünk van. Mivel B függvény egy belső függvény (az A függvényen belül van deklarálva), így számára hozzáférhető az A függvény összes változója. B függvényt most nem hívjuk meg az A függvény törzsében, ehelyett visszaadjuk azt. Pontosabban egy referenciát adunk vissza a B függvényre. A visszakapott függvény referenciát elmentjük, majd a következő sorban meghívjuk a B függvényt. Különös helyzet áll elő, hiszen az A függvény már befejezte futását, de a B függvény továbbra is hivatkozik az A függvény egyik változójára (futási környezetére).
Closure szemléltetése – a külső függvény visszaadja a belső függvényt, ami továbbra is élvezi a külső függvény hatókörét
A külső függvény végrehajtási kontextusa a függvény futásának befejezése után sem szűnik, mivel a benne létrehozott belső függvény továbbra is hivatkozik rá. Ezt nevezzük lezárásnak (lexikális zárvány). Ez annyit jelent, hogy egy belső függvényből mindig elérhetők a külső függvény változói, még akkor is, ha a külső függvény már lefutott.
A lezárás a JavaScript nyelv nagy erőssége. Más nyelveken programozók ezt szokták a JavaScript nyelv legnagyobb nehézségei között emlegetni, és a JavaScript nyelvben még nem eléggé jártas programozók ezzel a témával szoktak a legnagyobb gondban lenni.
Gyakran elkövetett hiba, hogy a programozó arra számít, hogy a külső függvény változóinak aktuális értékéről másolat készül és a belső függvényben azok lesznek elérhetők. A valóságban a belső függvény referencián keresztül hivatkozik a külső függvény változóira.
unction foo(){
var bar = 1;
return function(){
return bar++;
};
}
var baz = foo();
baz(); // 1
baz(); // 2
var thud = foo();
thud(); //1
thud(); //2;
Lássunk egy következő példát.
function openLinksInNewWindows(){
for(var i=0; i < document.links.length; i++){
document.links[i].onclick = function(){
window.open(document.links[i].href);
return false;
};
}
}
Egy eseménykezelő függvényt definiálunk az openLinksInNewWindows függvényen belül. A belső függvény tehát eléri a külső függvény változóit. Az i változó értéke az eseménykezelő meghívásakor azonban nem a nekünk szükséges értéket fogja tartalmazni. Az openLinksInNewWindows függvény a HTML dokumentumban lévő összes linkhez (<a> tag) egy eseménykezelő függvényt rendel. A példa kedvéért tételezzük fel, hogy HTML dokumentumunkban 10 hiperhivatkozás van, azaz a document.links.length tulajdonság 10-et tartalmaz. A for ciklus összesen tízszer fog lefutni, az i számláló értéke legvégül 10 lesz. Emlékezzünk vissza, hogy lezárás jön létre, ezért az openLinksInNewWindows függvény visszatérésekor a futási környezet nem szűnik meg. Az eseménykezelő függvények jóval az openLinksInNewWindows() függvény lefutása után lesznek meghívva (amikor a felhasználó rákattint valamelyik linkre az oldalon). Ekkora azonban az i számláló értéke a 10-es számot fogja tartalmazni. Az eseménykezelő hibát fog generálni, mert az oldalon lévő utolsó link indexe 9-es (nincsen 10-es indexű link).
A megoldás (a következő fejezet elolvasása után érteni fogjuk) a this kulcsszó használata.
document.links[i].onclick = function(){
window.open(this.href);
return false;
}
A lezárást sok mindenre felhasználhatjuk. Következzen néhány példa lezárás felhasználására.
Információ elrejtés
A következő példa azt mutatja be, hogyan lehetnek objektum literállal létrehozott objektumnak privát láthatóságú változói illetve függvényei (/closure/private-members-in-object-literal-objects.htm)
var simon = (function(){
var myVar = 5; // private valtozo
function init(x){
// hozzáfér myVar és doPrivate-hez
}
function doPrivate(x){
// private függvény, azaz lathatatlan a külvilág számára
}
function doSomething(x,y){
// hozzáfér myVar és doPrivate-hez
}
return {
'init' : init,
'doSomething' : doSomething
};
})();
Gyártó függvény (Factory Method)
A következő példa egy jól ismert programtervezési mintát valósít meg lezárás felhasználásával.
function szazalekSzamitoFactory(szazalek){
function fnc(osszeg){
return (szazalek / 100) * osszeg;
}
return fnc;
}
var jovedelemAdoSzamolo = szazalekSzamitoFactory(19);
var kolcsonKamatSzamolo = szazalekSzamitoFactory(2);
Az ilyen jellegű megvalósítást, hogy egy függvény „legyárt” nekünk egy másik függvényt, factory-nak hívjuk.
A lezárás a JavaScript egyik leghatékonyabb mechanizmusa, azonban kellő óvatosság és ismeretek nélkül potenciális veszélyek forrása is lehet. A memóriaszivárgás fejezet foglalkozik ezekkel a veszélyforrásokkal.