Definicija i karakteristike

closure in debiger

Poznata je činjenica da kada se završi funkcija, sve njene lokalne promenljive pokupi garbage collector i one prestaju da postoje u memoriji. Medjutim to nije slučaj za funkciju koja unutar sebe sadrži closure funkciju.

Closure su funkcije koje imaju pristup promenjivima koje se nalaze u domenu druge funkcije. Ovo se najčešće postiže ugradjivanjem funkcije unutar druge funkcije (čime se definiše “scope chain”), nakon čega closure dobija sposobnost da zapamti referencu na promenjive iz parent domena.

Treba napomenuti da se promenjive spoljne funkcije ne brišu po izvršavanju same funkcije, već se čuvaju u memoriji da bi bile dostupne closure funkciji. Nakon izvršenja closure funkcije, zatvara se i spoljna funkcija (odatle i naziv “closure“). Dok god se closure ne izvrše, JavaScript će čuvati i potrebne promenjive iz domena drugih funkcija, stoga one zauzimaju više memorije nego obične funkcije. Preterano korišćenje closure može da dovede do povećenja potrošnje memorije. Najnoviji JavaScript engine uspevaju da delimično oslobode zarobljenu memoriju, ali je i dalje stoji preporuka da se bude pažljiv pri korišćenju closure.

U sklopu debuger-a ugradjenog u browseru, može da se vidi closure izraz i koja je vrednost pormenjive “zapamćena” (pogledaj sliku).

Primer standardne closure f-je

Funkcija b() je ugnježdena unutar funkcije a() i duž “scope chain” potražuje promenjivu iz funkcije a(), stoga je ona klasičan primer closure funkcije.

Nije svaka ugnježdena funkcija i closure

Ukoliko unutrašnja funkcija ne koristi promenjivu iz spoljnih funkcija u kojim se nalazi (duž “scope chain”), to onda nije closure. Pogledaj naredni primer:

Closure pamti promenjive iz spoljne funkcije čak iako je spoljna fukcija izvršena

Poznato je da se nakon korišćenja naredbe return prekida izvršenje funkcije i postojanje njenih promenjivih u privremenoj memoriji. Medjutim “Closure” pamti promenjive iz spoljne funkcije čak iako je vratila neku vrednost sa return.

Closure “pamti” referencu na koju ukazuje promenjiva

Kada se kaže da “closure funkcija” ima pristup promenjivoj pod tim se misli da ima pristup odredjenom mestu u memoriji na koju ukazuje ta promenjiva tj. njenoj referenci u trenutku pozivanja “clousure funkcije“. Ukoliko se na tom mestu u memoriji tj. referenci menja vrednost, clousure će uvek uzeti najnoviju trenutnu vrednost. U narednom primeru se vidi kako unutrasnja funkcija “pamti” referencu i koristi trenutnu vrednost u memoriji.

Redeklarisanje promenjive

Ako nakon definisanja clousure, promenjivoj koja je potrebna closure dodelimo neku drugu referencu (mesto u memoriji koje čuva neku vrednost), clousure će da koristi referencu koja je bila u opticaju u trenutku definisanja clousure. U sledećem primeru, funkcija sayHello() u trenutku definisanja “pamti” referencu na globalnu promenjivu “myName” (“Dragoljub”). Pa iako “myName” menja referencu koja sada sadrži vrednost “Marko”, closure i dalje koristi vrednost “Dragoljub”.

Medjutim to ne znači da closure pamti prvu definisanu vrednost neke promenjive, već pamti definisanu vrednost promenjive u trenutku definisanja closure. Pa tako u sledećem primeru funkcija vraća “Marko”

NAPOMENA:
Kada se koristi ključna reč “new” za kreiranje objekta unutar neke spoljne funkcije, TO NE KREIRA CLOSURE i nova funkcija nema referencu na lokalnu promenjivu spoljne fukncije kao closure.

Primena closure funkcije

Rešavanje problema sa “this” kod callback funkcije u petlji

Callback funkcija u petlji daje neželjene rezultate jer se ne poziva odmah dok petlja vrti, već uvek “malo kasnije” kada je petlja već izvrtela i došla do poslednje vrednosti brojača. Stoga callback funkcija uvek koristi poslednju vrednost brojača. Ovaj problem se rešava tako što callback funkciju odmah pozovemo (stavimo je u IIFE) da bi ona “zgrabila” vrednost promenjive “i” u tom trenutku, dok će closusure odraditi svoj deo posla i zapamtiti koja je to bila vrednost. Na ovaj način pravimo za svaki prolaz kroz petlju novu closure funkciju, koja “pamti” drugačiju vrednost promenjive “i”.

Primer br.1 – callback funkcija setTimeout() metode

Po startovanju petlje početna vrednost promenjive “i=0”, nakon čega se poziva funkcija setTimeout(). Funkcija setTimeout() će tek za 10s pozvati callback funkciju. Dok se čeka na pozivanje callback funkcije, petlja nastavlja da “vrti” i sada je “i=1”, odmah zatim se poziva se funkcija setTimeout() koja će za 10s opet pozvati callback funkciju, a dok se čeka na njeno izvršenje, petlja vrti dalje i sada je “i=2”. Postupak se ponavlja sve dok “i” ne dobije vrednost 5. Ovaj ceo prethodno pomenuti postupak je izvršen veoma kratkom periodu (mereno milisekundama).
Nakon prvih 10sec petlja već “izvrtela” promenjivu “i” i “poziva” se (invoke) prva callback funkcija koja stoga uzima vrednost promenjive “i” a to je 5. Naredna callback funkcija za se aktivira za dodatnih 10sec, i koristi istu promenjivu pa je rezultat je isti…

Rešenje problema:

U ovom primeru pozivamo funkciju pri svakom krugu petlje uz pomoć IIFE, i tako joj obezbedjujemo jedinstvene vrednosti za svaki loop.

Primer br. 2 – callback funkcija addEventListener()

Na sledećem primeru se javlja sličan problem kao kod petlje i funkcije setTimeout():

See the Pen VWPNPq by Web programiranje (@chos) on CodePen.

Kao u svakom standardnoj petlji na svaki element je dodat eventListener i petlja se završila. U trenutku kad se klikne na neko dugme petlja je već “izvrtela” i koje god dugme da se izabere alert će da bude jednak.

Rešenje

Jedno od mogućih rešenja je da se stavi ceo kod u IIFE koja će se pozivati pri svakom krugu a clousure će da pamtiti prosledjene vrednost:

See the Pen gRgyOy by Web programiranje (@chos) on CodePen.

Imitacija privatnih podataka

JavaScript je jezik koji ne podržava privatne podatke u istom smislu kao neki drugi programski jezici, ali koristeći closure možemo da imitiramo ovakvo ponašanje. Sve se zasniva na osobini closure da ona jedina ima pristup promenjivoj u spoljnoj funkciji i nakon što je funkcija završena. Pošto se takvoj promenjivoj može pristupiti jedino iz closure funkcije, može se smatrati da je promenjiva privatna.

Konstruktorski šablon za kreiranje privatnih podataka

U narednom kodu je prikazana standardna konstruktorska funkcija sa svojstvom objekta “_name”.

Promenjiva “name” nije privatna jer postoji mogućnost pristupa iz globalnog domena. Nakon kreiranja novog objekta je moguće promeniti vrednost promenjive sa jednostavnim dodeljivanjem vrednosti svojstvu objekta.

Ako želimo da imitiramo dobru praksu objektnog programiranja iz drugih jezika i sprečimo pristup ovoj promenjivoj iz globalnog domena, koristićemo closure i definisaćemo lokalnu promenjivu umesto svojstva objekta.

Sada jedini način da se “zapamti” vrednosti promenjive pri stvaranju novog objekta, je preko closure getName(). I dalje postoji mogućnost da se pristupi svojstvu objekta i da se promeni vrednost, ali closure vraća vrednost promenjive pri konstruisanju objekta. Ovo je objašnjeno u delu redeklarisanje promenjive.

PRIVILEGOVANE METODE:
Privilegovanje metode su globalne metode koje imaju pristup privatnim promenjivama ili funkcijama.

U sledećem primeru kreiramo “privilegovane metode” koje mogu da menjaju privatne promenjive.

Mana ovog postupka je što je za primenu neophodno koristiti konstruktorski šablon koji pri svakom instanciranju novog objekta instanciraju i ove dve metode (getName i setName), ostale metode mogu to izbeći korišćenjem prototype patern-a.

NAPOMENA:

Najbolja implementacija privatnih svojstava u JS-u je konačno dodata u ES2022 sa hash # prefiksom.

Podelite:

5 Responses to “Šta su JavaScript closure?”

  1. poslednja dva reda u komentarima kazu da je kreiranje persone broj 2 prepisalo podatke u prvom objektu?
    alert(person1.getName()); //”Mirka”
    alert(person2.getName()); //”Mirka”

    • Dragoljub

      Upravo tako…ubaci ceo kod u konzolu i dobićeš taj rezultat, sa ovim primerom sam hteo da pokažem kako se clousure može koristiti za kreiranje statičnog svojstva (svojstvo koje je dostupno svim instancama).

  2. Vuk Mistovic

    Pozdrav! Zasto je u poslednjem primjeru, name definisano van funckije Person. Ne razumem ovakvu upotrebu, jer svaki put kada kreiramo novi objekat, prethodna vrijednost varijable name ce biti pregazeno.

    • Dragoljub Živanović

      Vuče hvala na komentaru,
      u tom delu sam hteo da objasnim “statična” svojstva, ali očigledno da tome nije mesto u ovome članku jer u ovome kontekstu privatnih promenjivih samo stvara zabunu, tako da sam taj deo izbacio ali i ažurirao privatna svojstva sa najnovijim JS pristupom.

Ostavite komentar