Skip to content

Latest commit

 

History

History
99 lines (72 loc) · 3.85 KB

File metadata and controls

99 lines (72 loc) · 3.85 KB

Closure-ök és referenciák

A JavaScript nyelv egyik legerőteljesebb tulajdonsága a closure-ök használatában rejlik. Ezek használatával a hatókörök egymásba ágyazhatóak, és egy belső hatókör mindig hozzáfér az őt körülvevő, külső hatókör változóihoz. Miután JavaScriptben egyetlen dologgal lehet hatóköröket kifejezni, és ez a funkció (bizony az if, try/catch és hasonló blokkok nem jelentenek új hatókört, mint pl. a Javaban), az összes funkció closure-ként szerepel.

Privát változók emulálása

function Counter(start) {
    var count = start;
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
    }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

Ebben a példában a Counter két closure-rel tér vissza: az increment és a get funkcióval. Mind a két funkció referenciát tárol a Counter hatókörre, és így mindketten hozzáférnek a count változóhoz, ami ebben a hatókörben lett definiálva.

Miért működnek a privát változók?

Mivel a JavaScriptben egyszerűen nem lehet hatókörre referálni, vagy hatókört értékül adni, így ezért szintén lehetetlen elérni az iménti count változót a külvilág számára. Egyetlen mód van a megszólítására, ezt pedig láttuk a fentebbi két closure-ön belül.

var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

A fentebbi kód nem fogja megváltoztatni a Counter hatókör count változóját, mivel a foo.hack mező nem abban a hatókörben lett létrehozva. Ehelyett, okosan, létre fogja hozni, vagy felül fogja írni a globális count változót (window.count).

Closure-ök használata ciklusokban

Az egyik leggyakoribb hiba amit el lehet követni, az a closure-ök ciklusokban való használata. Annak is azon speciális esete amikor a ciklus indexváltozóját szeretnénk lemásolni a closure-ön belül.

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

A fenti kódrészlet marhára nem a számokat fogja kiírni 0-tól 9-ig, de inkább a 10-et fogja tízszer kiírni.

Ugyanis a belső névtelen függvény egy referenciát fog tárolni a külső i változóra, és akkor, amikor végül a console.log sor lefut, a for loop már végzett az egész ciklussal, így az i értéke 10-re lesz beállítva.

Ahhoz, hogy a várt működést kapjuk (tehát a számokat 0-tól 9-ig), szükségszerű az i változó értékét lemásolni.

A referencia probléma elkerülése

Az előző problémára megoldást úgy lehet jól adni, hogy az utasításoknak megfelelően lemásoljuk a ciklusváltozót, úgy hogy a jelenlegi ciklusmagöt körbevesszük egy névtelen függvénnyel.

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

A külső (wrapper) névtelen függvény így azonnal meghívódik az i ciklusváltozóval, mint paraméterrel, és így mindig egy másolatot fog kapni az i változó értékéről, amit ő e néven emészt tovább.

Így a setTimeoutban lévő névtelen fgv. mindig az e nevű referenciára fog mutatni, aminek az értéke így már nem változik meg a ciklus futása során.

Egy másik lehetséges út a megoldáshoz az, hogy egy wrapper függvényt visszatérítünk a setTimeoutból, aminek ugyanaz lesz a hatása, mint a fentebbi példának.

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}