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

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.

A témához kapcsolódó linkek: