JavaScript - Closures


Razumijevanje JavaScript zatvaranja (closures)

U lekciji JavaScript funkcije saznali ste da opseg varijable može biti globalni ili lokalni. Od ES6 takođe možete kreirati varijable s opsegom bloka pomoću ključne riječi let. Globalnoj varijabli može se pristupiti i njom se manipulira bilo gdje u programu, dok lokalnoj varijabli može se pristupiti i manipulisati samo u funkciji u kojoj su deklarisani. Međutim, postoje određene situacije kada želite da varijabla bude dostupna u cijeloj skripti, ali ne želite da bilo koji dio vašeg koda može slučajno promijeniti vrijednost. Pogledajmo što će se dogoditi ako to pokušate postići pomoću globalne varijable:

// Globalna varijabla
var counter  = 0;

// Funkcija namijenjena manipulaciji varijablom 'counter'
function makeCounter() {
    return counter += 1;
}

// Pozivanje funkcije
makeCounter();
console.log(counter); // Ispisuje: 1

makeCounter();
console.log(counter); // Ispisuje: 2

// Pokušaj manipulacije varijablom 'counter' izvana
counter = 10;
console.log(counter); // Ispisuje: 10
Pogledajmo kako koristiti primjer u praksi:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JavaScript Manipuslisanje globalnim varijabla</title>
</head>
<body>
    <script>
        // Globalna varijabla
        var counter  = 0;
        
        // Funkcija namijenjena manipulaciji varijablom 'counter'
        function makeCounter() {
            return counter += 1;
        }
        
        // Pozivanje funkcije
        makeCounter();
        document.write(counter + "<br>"); // Ispisuje: 1
        
        makeCounter();
        document.write(counter + "<br>"); // Ispisuje: 2
        
        // Pokušaj manipulacije varijablom 'counter' izvana
        counter = 10;
        document.write(counter); // Ispisuje: 10
    </script>
</body>
</html>

Kao što možete vidjeti u gornjem primjeru, vrijednost varijable brojača "counter" može se mijenjati s bilo kog mjesta u programu, bez pozivanja funkcije makeCounter(). Pokušajmo sada postići isto sa lokalnom varijablom i vidjeti što će se dogoditi:

function makeCounter() {
    // Lokalna varijabla
    var counter  = 0;
    
    // Manipulisanje varijablom 'counter'
    return counter += 1;
}

// Pozivanje funkcije
console.log(makeCounter()); // Ispisuje: 1
console.log(makeCounter()); // Ispisuje: 1
Pogledajmo kako koristiti primjer u praksi:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JavaScript Manipuslisanje lokalnim varijablama</title>
</head>
<body>
    <script>
        function makeCounter() {
            // Lokalna varijabla
            var counter  = 0;
            
            // Manipulisanje varijablom 'counter'
            return counter += 1;
        }
        
        // Pozivanje funkcije
        document.write(makeCounter() + "<br>"); // Ispisuje: 1
        document.write(makeCounter()); // Ispisuje: 1
    </script>
</body>
</html>

U ovom slučaju varijablom brojača "counter" nije moguće manipulisati izvana, jer je lokalno funkciju makeCounter(), ali se njena vrijednost takođe neće povećavati nakon sljedećeg poziva funkcije, jer svaki put kada pozovemo funkciju ona resetira vrijednost varijable brojača "counter" koju ste vidjeti u gornjem primjeru. Zatvaranje JavaScript-a može riješiti naš problem.

Zatvaranje je u osnovi unutrašnja funkcija koja ima pristup opsegu nadređene funkcije, čak i nakon završetka izvršenja nadređene funkcije. To se postiže stvaranjem funkcije unutar druge funkcije. Pogledajmo sljedeći primjer kako bismo vidjeli kako to funkcionira:

function makeCounter() {
    var counter = 0;
    
    // Ugnježdena funkcija
    function make() {
        counter += 1;
        return counter;
    }
    return make;
}

/* Izvršavanje funkcije makeCounter() i spremanje
vraćena vrijednost u varijabli myCounter */
var myCounter = makeCounter();

console.log(myCounter()); // Ispisuje: 1
console.log(myCounter()); // Ispisuje: 2
Pogledajmo kako koristiti primjer u praksi:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JavaScript Closures (Zatvaranja)</title>
</head>
<body>
    <script>
        function makeCounter() {
            var counter = 0;
            
            // Ugnježdena funkcija
            function make() {
                counter += 1;
                return counter;
            }
            return make;
        }
        
        /* Izvršavanje funkcije makeCounter() i spremanje
    vraćena vrijednost u varijabli myCounter */
        var myCounter = makeCounter();
        
        document.write(myCounter() + "<br>"); // Ispisuje: 1
        document.write(myCounter()); // Ispisuje: 2
    </script>
</body>
</html>

Kao što možete vidjeti u gornjem primjeru, unutrašnja funkcija make() vraća se iz vanjske funkcije makeCounter(). Dakle, vrijednost myCounter-a je unutrašnja funkcija make(), a pozivanje myCounter-a učinkovito poziva make(). U JavaScript funkcije se mogu dodijeliti varijablama, prosljeđuju se kao argumenti drugim funkcijama, mogu se ugnijezditi unutar drugih funkcija i još mnogo toga.

Takođe ćete primijetiti da unutrađnja funkcija make() još uvijek može pristupiti vrijednosti varijable brojača definiranoj u vanjskoj funkciji, iako je funkcija makeCounter() već završila s izvršavanjem. To se događa zato što se funkcije u JavaScript obliku zatvaraju. Zatvarači interno pohranjuju reference na svoje vanjske varijable i mogu pristupiti i ažurirati njihove vrijednosti.

U gornjem primjeru funkcija make() je zatvaranje čiji se kod odnosi na vanjski brojač varijabli. To implicira da kad god se pozove funkcija make(), kod unutar nje može pristupiti i ažurirati varijablu brojača jer je pohranjen u zatvaranju. Konačno, budući da je vanjska funkcija završila s izvršavanjem, niti jedan drugi dio koda ne može pristupiti ili manipulisati varijablom brojača. Samo unutrašnja funkcija ima ekskluzivan pristup. Prethodni primjer se takođe može napisati pomoću anonimnog izraza funkcije, poput ovog:

// Anonimni izraz funkcije
var myCounter = (function() {
    var counter = 0;
    
    // Ugnježdena anonimna funkcija
    return function() {
        counter += 1;
        return counter;
    }
})();

console.log(myCounter()); // Ispisuje: 1
console.log(myCounter()); // Ispisuje: 2
Pogledajmo kako koristiti primjer u praksi:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JavaScript Konstrukcija zatvaranja koristeći sintaksu izraza funkcije</title>
</head>
<body>
    <script>
        // Anonimni izraz funkcije
        var myCounter = (function() {
            var counter = 0;
            
            // Ugnježdena anonimna funkcija
            return function() {
                counter += 1;
                return counter;
            }
        })();
        
        document.write(myCounter() + "<br>"); // Ispisuje: 1
        document.write(myCounter()); // Ispisuje: 2
    </script>
</body>
</html>


Stvaranje Getter i Setter funkcija

Ovdje ćemo stvoriti varijable secret i protect od direktne manipulacije vanjskim kodom pomoću zatvaranja (closure). Takođe ćemo stvoriti getter i setter funkcije kako bismo dobili i postavili njegovu vrijednost. Pored toga, funkcija setter takođe će izvršiti brzu provjeru da li je navedena vrijednost broj ili ne, a ako nije, neće promijeniti vrijednost varijable.

var getValue, setValue;

// Funkcija samoizvršavanja
(function() {
    var secret = 0;
    
    // Getter funkcija
    getValue = function() {
        return secret;
    };
    
    // Setter funkcija
    setValue = function(x) {
        if(typeof x === "number") {
            secret = x;
        }
    };
}());

// Pozivanje funkcija
getValue(); // Vraća: 0
setValue(10);
getValue(); // Vraća: 10
setValue(null);
getValue(); // Vraća: 10
Pogledajmo kako koristiti primjer u praksi:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JavaScript Getter i Setter funkcije</title>
</head>
<body>
    <script>
        var getValue, setValue;

        // Funkcija samoizvršavanja
        (function() {
            var secret = 0;
            
            // Getter funkcija
            getValue = function() {
                return secret;
            };
            
            // Setter funkcija
            setValue = function(x) {
                if(typeof x === "number") {
                    secret = x;
                }
            };
        }());
        
        // Pozivanje funkcija
        document.write(getValue() + "<br>"); // Ispisuje: 0
        setValue(10);
        document.write(getValue() + "<br>"); // Ispisuje: 10
        setValue(null);
        document.write(getValue()); // Ispisuje: 10
    </script>
</body>
</html>