Lezárás (closure)
Mielőtt rátérnék a lezárás fogalmának elmagyarázásához, frissítsük fel és bővítsük ki az eddigi ismereteinket
a scope és scope chain fogalmakról.
Más programozási nyelvekhez hasonlóan, JavaScriptben az azonosítók (változók nevei, függvények nevei, objektumok,..)
csak egy bizonyos érvényességi tartományon belül láthatók. Ezt angolul scope-nak hívják. Az érvényességi tartomány (scope) a kód azon
részét jelöli, amelynek határain belül az adott azonosítót használhatjuk.
Azok a változók, amelyeket minden függvényen kívül deklarálunk, globális érvényességi tartományt (global scope) kapnak.
A globális érvényességi tartománnyal rendelkező azonosító a kód bármely részéből elérhető (egy JavaScript- ill. HTML-fájlon belül).
var globalisValtozo = "elerheto vagyok barhonnan";
function F(){
alert(globalisValtozo);
}
A window objektum globális scope objektumként viselkedik. Minden globális érvényességi tartománnyal rendelkező azonosító a window objektum tulajdonsága.
var foo = 'bar';
window.baz = 'thud';
alert(window.foo); // alerts 'bar'
alert(baz); // alerts 'thud'
A globális objektum (window) továbbá tulajdonságokként tartalmazza az összes beépített objektumot (String, Number, Date,...), függvényeket (parseInt(),...) valamint a befogadó környezet (böngésző stb.) által nyújtott objektumokat is.
Minden egyes függvény objektum rendelkezik egy [[scope]] nevű belső tulajdonsággal, mely az objektum létrehozásakor kap értéket attól függően, hogy az milyen környezetben jön létre. Ez igazából egy akár több objektumból álló lista (scope chain) hasonlóan a prototípus lánchoz. A [[scope]] tulajdonság által tárolt lista alapján történik az adott futási környezetben az azonosítók feloldása. Vizsgáljuk meg, hogy az előző példában az F függvény törzséből hogyan érhető el a globalisValtozo változó.
- Egy futási környezetben az azonosítók feloldása a this kivételével úgy történik, hogy először a hozzá tartozó érvényességi
lista első objektumának tulajdonságaként keresi a futtató környezet.
F.[[scope]][0].globalisValtozo // undefinedA konkrét példánkban az F függvény [[scope]] tulajdonság által tárolt objektum lista első eleme az adott függvényben definiált lokális változókat tartalmazza. Ez az objektum tulajdonképpen egy Variables nevű pszeudo objektum. Az ő tulajdonságaiként jönnek létre az adott függvényben definiált lokális változók (a függvény formális paraméterei; a függvényen belüli függvény deklarációk (ezek esetében a függvény neve lesz a létrejövő tulajdonság neve); a változó deklarációk). - Amennyiben az érvényességi lista első objektuma nem tartalmazza a keresett azonosítójú tulajdonságot, második lépésként az objektum prototípus láncában lesz keresve az azonosító.
- ha az érvényességi lista első elemében nem bukkant rá a keresett tulajdonságra, akkor veszi a lista következő elemét. A mi konkrét példánkban ez a lépés is lefut. Az érvényességi lista következő (utolsó) eleme a globális objektum. A globális objektumnak van az azonosító nevével egyező (globalisValtozo) tulajdonsága, így ennek referenciája (értéke) lesz az azonosító.
Azonosítók feloldása
Lássunk egy további példát.
<script type="type/javascript">
// Ezen a ponton a globális futási környezetben vagyunk, melynek
// érvényességi listájában csak a globális objektum lesz.
// A Variables pszeudo objektum a globális objektum lesz, melynek
// lesz egy foo és egy B tulajdonsága.A foo értéke itt még
// undefined, míg a B értéke egy Function objektum lesz, aminek
// a [[scope]] tulajdonsága megkapja az aktuális futási környezet
// érvényességi listáját, tehát abban is a globális objektum lesz.
var foo = 'bar'; // globális érvényességű változó
// Itt a foo tulajdonság értéke undefined-ról 'bar'-ra változik.
// A függvényen belüli kommentek arra az esetre szólnak,
// amikor az meghívódott.
function B() {
// a lokális foo elfedi a globális foo-t.
var foo = 'thud';
// A láthatósági lista alapján ki kell keresni a foo azonosító
// értékét. Ebben most az Activation és a globális
// objektum van. Az Activation objektumban van foo tulajdonság,
// igy nem kell tovább keresni.
// A foo értéke "thud" lesz.
alert(foo);
}
alert(foo); // 'bar'
B(); // 'thud'
alert(foo); // 'bar'
JavaScriptben egy függvényen belül deklarálhatunk további ún. belső függvényeket ((inner functions)). Az egymásba ágyazott függvények egyik legfontosabb tulajdonsága, hogy a belső függvényből elérhetők a külső függvény változói (a külső függvény paraméterei is, ha vannak). Ezt angolul „Static Scopin” vagy „Lexical Scoping” -nak is nevezzük.
function SayHello(name) { // külső függvény
var foo = 'Hello ';
function e() { // belső függvény
alert(foo + name + ' !');
}
e();
}
SayHello('world'); // 'Hello world !'
Az egymásba ágyazott függvények sok hasznos dologra használhatók. Például jól megtervezett, könnyebben átlátható kód írásának egyik hasznos eszköze lehet. Hosszabb, összetettebb kód írásakor sokszor csábító globális változókat deklarálni adatmegosztás céljából függvények között. Ehelyett jobb megoldás lehet a változókat és a függvényeket egy függvényen belül deklarálni. A belső függvények az adatmegosztásra használt változókat elérik, és a globális névteret sem szennyeztük.