Simbioza JavaScripta i njegovog okruženja

Uvod

asnhorni JS okruzenje

JavaScript je skriptni jezik, stoga je neophodno da se “smesti” u neki kontejner koji bi mu omogućio rad, taj kontejner se zove JS Runtime Environment (srp. okruženje). Postoje dva tipa okruženja: browser (za rad na klijentu) i node.js (za rad na serveru). Runtime Environment čine :

  1. JS Engine/Runtime
    • Heap
    • Stack
  2. External API
  3. Queue
  4. Event loop

OBJAŠNJENJE:
JavaScript je “single thread” programski jezik i on sam ne može da izvršava više istovremenih paralelnih radnji. Ipak izvršavanje više paralelnih radnji (tzv. asihrono izvršavanje) je moguće uz medjusobnu saradnju JavaScript-a i njegovog okruženja. Više o ovome prolitajte u članku “Princip rada asinhronog JavaScript-a”.

JS engine/runtime

JS runtime/engine je aplikacija pisana u C++ koja kompajlira JavaScript kod u mašinski kod. Kompajliranje se vrši pri svakom startovanju programa tzv. “just-in-time compilation”. Najpoznatiji JS Engine je Google-ov “V8” koji se inače nalazi u Chromu ali i u Node.js-u. Ostali engine su: Mozilin “Rhino”, Firefox-ov “SpiderMonkey”, Microsoft-ov “Chakra”… JS Runtime za rad koristi dva tipa memorije: Heap i Stack.

Memorija

a) Stack

stack

“Stack” je linearno struktuirana memorija, koja je “brza” i služi za privremeno čuvanje podataka (prostih primitiva,funkcija…) pri izvršavanju koda. Podaci se smeštaju u memoriju sekvencijalno, tako da “noviji” podaci dolaze preko “starijih”. Često se za ovaj tip organizovanja podataka pravi paralela sa gomilom poredjanih sudova jedan na drugi. Kako prestane potreba za nekim podatkom na vrhu stack-a, njega garbage collector sklanja sa stack-a. Ovakva organizacija podrazumeva da se može pristupiti trenutno najnovijim podacima. Što znači da se ne može pristupiti nekim starijim podacima pre nego što se uklone svi noviji podaci.

Pogledajte animaciju rada callstack-a ovde (Savet: kliknite na ikonicu dva ukrštena čekića u gornjem levom uglu aplikacije da promenite brzinu izvršavanja koda).

Napomena:
Obratite pažnju da stack može da primi jedan odredjeni broj taskova, pa ukoliko pozovemo previše taskova javiće se greška “Range Error: Maximum CalStack size exceeded”.

b) “Heap”

Heap predstavlja ne struktuirani tip memorije u koji se smeštaju i čuvaju dinamički podaci (objekti). Ovo je sporiji tip memorije. Za razliku od stack-a koji ima ograničenu veličinu, “heap je ograničen samo veličinom virtuelne memorije. Podaci čuvani na ovoj vrsti skladišta nemaju medjusobnih zavisnosti i može im se pristupiti bilo kojim redom u bilo koje vreme. Svaki podatak ima svoju adresu preko koje možemo da ga pronadjemo.

External API

Već smo pomenuli da je JavaScript skriptni jezik, i da je neophodno da se “smesti” u neki kontejner koji bi mu omogućio rad. Pod External API-jem se najčešće podrazumevaju objekti i metode koje omogućavaju komunikaciju JavaScript-a sa spoljnim svetom. External API zavisi od okruženja, pa se tako razlikuje API borwser-a od APi-ja NODE.js. Spisak svih Web API-ja možete pogledati na stranici https://developer.mozilla.org/en-US/docs/Web/API

Browser API

Node.je API

  • Process objekat
  • Buffer objekat
  • require()
  • setTimeout()

Third party API

Pored API ja okruženja (browser-a ili Node.js), postoje i API-ji od spoljnih resursa tzv. Third party API koji su neophodni ako želimo da koristimo njihove usluge. Najpoznatiji “Third party API” su:

  • Google Maps
  • Twitter
  • Facebook
  • PayPal

Queue

Ovaj tip memorije ima linearnu strukturu a rad sa podacima je takodje sekvencijalan kao kod stack-a. Smeštanje podataka je isto kao kod stack-a ali je zato pristup podacima prema obrnutom redosledu od najstarijeg podatka ka novijem.
Ovde se smešta deo koda koji je spreman da se pošalje na izvršavanje JS Engine-u. Ova memorija se oslobadja sekvencijalno tako što se odredjeni deo koda pošalje na izvršavanje JS Engine-u i prebaci na stack. Ali kada će se to desiti “odlučuje” Event loop (ovo se dešava tek kada je stack izmedju dve operacije ostao prazan). Queue se još često naziva “Event queue” ili “Callback queue” u zavisnosti sa čime je napunjen.

Event loop

Event loop je proces koji stalno proverava da li je stack prazan i da li postoje neke funkcije u queue da se izvrše. Ukoliko se to desi poziva se prva na redu funckcija iz queue (“najstarija”) i prebacuje se na stack.

Pogledajte animaciju rada JavaScrip-a ovde, a više o asinhronom radu javascripta možete pročitati u članku Princip rada asinhronog javascript-a


Princip rada asinhronog JavaScript-a

Uvod

JavaScript je “single thread” programski jezik i on sam ne može da izvršava više istovremenih paralelnih radnji. JavaScript je takodje i skriptni jezik stoga je neophodno da se “smesti” u neki kontejner koji bi mu omogućio rad, taj kontejner se zove JS Runtime Environment (srp. okruženje). Upravo je to okruženje zaslužno za izvršavanje više paralelnih radnji istovremeno (tzv. asihrono izvršavanje). Ključni delovi zaduženi za asihroni rad JS-a su “External API”, i “Event loop”. Stoga da bi razumeli kako JavaScript obradjuje asinhrone pozive, neophodno je prvo upoznati osnovne elemente Runtime Enviroment-a. Više o JS Runtime Enviroment-u pročitajte u članku “JavaScript i njegovo okruženje”

asihrono programiranje

Pri sinhronom izvršavanju operacija, trenutna operacija mora da se završi da bi naredna počela da se izvršava. Dok kod asihronog izvršavanja, operacije ne čekaju jedna drugu da se izvrše, nego se izvršavaju istovremeno. Generalno u većini slučajeva sinhronomi procesi nisu problem jer su kompjuteri veoma brzi pa se sve operacije izvršavaju skoro instant. Medjutim kod web-a su problem upiti ka serveru. Ukoliko bi ovi procesi bili sinhroni, ceo interfejs web aplikacije bi bio “smrznut” dok ne stigne odgovor servera. Isto važi za I/O operacije sa podacima. Rezultat toga je da se ukupno asinhrone operacije brže izvršavaju iako svaka pojedinačno ništa nije brža nego kod sinhronih operacija. (Pogledaj sliku)

Asihrono programiranje sa setTimeout()

Asihrono programiranje sa odgovarajućim external API-jem je najlakše objasniti kroz primere. Naredni primer se izvršava u browseru, a kod sadrži dve metode za štampanje teksta u konzoli i jednu specijalnu metodu setTimeout() koja je deo objekta Window (WebAPI).

Primer

JS izvšava kod liniju po liniju, prvo stavi na stack prvu console.log() metodu i zatim je izvrši. Nakon čega JS engine dolazi do reda gde se poziva setTimeout(), pa ovu metodu stavlja na vrh stack-a i izvršava je.

eventloop1

Ovde počinje interesantni deo jer nakon izvršenja setTimeout() metode ona se sklanja sa stack-a, a njena callback funkcija se prebacuje u sekciju webAPI i uključuje tajmer. Nakon toga JS engine nastavlja sa obradom naredne linije koda gde nailazi na drugu console.log() metodu koju stavlja na stack i izvršava je.

eventloop2

Dok se izvršava ostali deo programa, a po isteku vremena zadatog u tajmeru callback funkcija se prebacuje na task queue i tamo čeka da se stack izprazni da bi mogla da se izrši.

evemtloop3

Event loop će prebaciti task (callback funkciju) iz task queue samo ako je prazan stack.

eventloop4

Kada se callback funkcija nadje na stack-u izvršava se i nakon toga sklanja sa stack-a. Rezultat izvšavanja ovog koda je štampanje teksta u konzoli ovim redosledom:

  1. Prva console.log() metoda štampa: “Hi”
  2. Treća console.log() metoda štampa: “JSconfEU
  3. Druga console.log() metoda u okviru setTimout() metode štampa: “there

NAPOMENA:
Odlaganje callback funkcije za nula sekundi ne podrazumeva da će odmah da se izvrši. Callback funkcija mora da predje isti put kao i u prethodnom primeru (stack -> web API -> task queue -> stack), stim što će se u web APi sekciji zadržati nula sekundi. Medjutim u task queue će morati da sačeka dok se se stack ne isprazni. Ova “fora” se koristi kada želimo da se sinhroni proces postane asinhroni, tako što callback funkciju omotamo sa setTimout() metodom, a kao parametar stavimo 0ms.

Pogledajte animaciju ovog primera ovde (Savet: kliknite na ikonicu dva ukrštena čekića u gornjem levom uglu aplikacije da promenite brzinu izvršavanja koda).

Asihroni poziv servera

Pored tajmera koji omogućavaju asinhrono programiranje, asihron je i poziv serveru uz pomoć XMLHttpRequest objekta. Na sledećem primeru ćemo objasniti kako asinhrono funcioniše JavaScript pri pozivu servera:

Primer

Prvo se izvšava prva linija koda zbog čega se prebacuje na stack

server call

Kada se izvrši prva naredba ona se sklanja sa stack-a i izvršava se naredna a to je get() metoda koja bi trebalo da asinhrono “dovuče” podatke sa servera koristeći “HTTP GET request“.

server call

Kad se izvrši metoda get(), ona se sklanja sa stack-a, dok “callback” funkcija stoji sa strane i tako ne koči dalje izvršavanje JavaScript koda dok čeka podatke.

server call

Dok callback funkcija čeka podatke, paralelno se izvršava poslednja linija koda koja štampa u konzoli neki tekst.

server call

Kada je ištampan teks i izvršena poslednja naredba, ona se sklanja sa stack-a koji ostaje prazan. Za to vreme je callback funkcija dobila podatke i smestila se u red za čekanje “queue”.

server call

Čim se oslobodio stack, event loop prebacuje callback funkciju na stack i izvršava je.

server call

Rezultat izvšavanja ovog koda je štampanje teksta u konzoli ovim redosledom:

  1. Prva console.log() metoda štampa: “Hi”
  2. Treća console.log() metoda štampa: “SJS
  3. Druga console.log() metoda u okviru setTimout() metode štampa podatke sa servera

Dogadjaji kod JS-a se izvršavaju asinhrono

Svi dogadjaji u JavaScriptu se izvršavaju asinhrono, jer pri kompajliranju koda JS engine stavlja callback funkcije vezane za dogadjaje sa strane i tako ne blokira izvršavanje ostalog koda.

Primer

U ovom primeru pri svakom kliku mišem se “okinu” tri dogadjaja i aktiviraju se tri callback funkcije koje ne blokiraju izvršavanje Javascript koda već se prosledjuju u “queue” gde čekaju da se isprazni stack.

asinhro event

Pogledajte animaciju ovog primera ovde (Savet: kliknite na ikonicu dva ukrštena čekića u gornjem levom uglu aplikacije da promenite brzinu izvršavanja koda).

NAPOMENA:
Ne blokirajte event loop!!! Ceo princip asinhronog rada JavaScript-a i Runtime Environment-a se zasniva na tome da se delovi koda prosledjuju iz “queue” na stack sa Event Loop-om, medjutim ukoliko je stack stalno zauzet (npr. neki veliki while loop, ili neki zahtevan rendering…), tada nismo u mogućnosti da izvršimo asinhronu radnju koja čeka u “queue”.


(Ne)moguće greške u JavaScript-u

Uvod

javascript error

JavaScript je specifičan programski jezik i u poredjenju sa drugim jezicima ponekad probleme rešava na drugačiji način od očekivanog, ali to ne znači da je taj način i pogrešan. Iza svake “čudne” odluke vezane za sintaksu jezika stoji dobar razlog zbog čega je tim koji radi na razvoju JavaScript-a baš nju izabrao. Da bi se razumele specifičnosti jezika potrebno je temeljno proučiti sintaksu i principe rada jezika. Postoji jedan manji broj nelogičnosti koje i dalje ne mogu tako lako da “svarim”, ali niko nije savršen pa tako ni JavaScript. Smatra se da programeri koji dodju u kontakt sa JavaScript-om kao drugim jezikom imaju malo više problema da se prilagode, jer očekuju iste principe kao u svom omiljenom programskom jeziku ali i ih ovde ne nalaze. U ovom članku sam sakupio potencijalne (ne)iznudjene greške koje može da napravi običan JavaScript smrtnik. Članak će se redovno ažurirati kako budem grešio 🙂

Čudni rezultati pri sabiranju decimalnih brojeva

Ovaj problem se dešava jer u nekim slučajevima decimalni brojevi ne mogu da se prikažu kao binarna frakcija (kao što npr. 1/3 ne može da se prikaže kao decimalna frakcija). Ovakvo ponašenje može da bude problem u izrazima, kao npr.:

Rešenje

Ukoliko se traži odredjena tačnost može da se koristi naredni postupak:

Ili

Poredjenje više vrednosti u istom izrazu

Gledano sa matematičke strane naredni snippet je po svemu netačan, medjutim ako se zna da je znak manje “<" u JavaScript-u operator komparacije sa svojim setom pravila postaje jasno zašto je rezultat TRUE:

Objašnjenje:

Asocijativnost operatora “<" je s’ leva na desno, pa izraz nakon grupisanja izgleda ovako:

Rešavanjem prve operacije dobija se logička vrednost FALSE, pa izraz izgleda

Pri poredjenju dva elementa različitog tipa dolazi do implicitne konverzije FALSE u broj “0” nakon čega izraz izgleda:

Više o operatorima pogledajte u članku “JavaScript operatori”

Hoisting funkcija

Za ovakav rezultat je odgovorno podizanje (eng. hoisting) funkcije na vrh bloka. Podizanje se vrši pre izvršenja i jedne linije koda pri kompajliranju koda (runtime). Prethodni kod nakon kompajliranja (gledano iz ugla mašine) se menja u:

Pogledajte više o hoisting-u funkcije u članku “Šta je hoisting?”

Korišćenje logičkih operatora sa “nelogičkim” operandima

U slučaju kada se sa logičkim operatorima (&& ili ||) porede operandi koji nisu logičkog tipa, zbog ispitivanja uslova operatora dolazi do implicitne konverzija operanada u logički tip. Medjutim operatori && ili || nakon razrešenja uslova ne vraćaju logičku vrednost, već vraćaju vrednost jednog od dva operanda. Razultat uslova se odredjuje prema sledećim pravilima:

  • Za operator || nakon konverzije “ne logičkog” tipa u logički pri razrešenju uslova važi pravilo da ukoliko je rezultat uslova operatora:
    • TRUE vraća vrednost prvog operanda
    • FALSE vraća vrednost drugog operanda
  • Za operator && nakon konverzije “ne logičkog” tipa u logički pri razrešenju uslova važi pravilo da ukoliko je rezultat uslova operatora:
    • FALSE vraća vrednost prvog operanda.
    • TRUE vraća vrednost drugog operanda
Objašnjenje rezultata primera:

Uvažavajući prioritete operatora koji su korišćeni, logičko “i” ima prednost nad logičkim “ili”, pa prethodni izraz može da se konvertuje u:

Za odredjivanje uslova u zagradi se prvo privremeno vrši implicitna konverzija tipova u logički tip, nakon toga se izvrši proracun sa operandom && nad dobijenim logičkim tipovima:

Rešenje uslova (TRUE && TRUE) je TRUE, pa stoga prema pravilu ukoliko je rezultat && TRUE operator vraća vrednost drugog operanda (u ovom slučaju “foo”), pa sredjeni izraz ovako izgleda:

Za odredjivanje uslova se prvo privremeno vrši implicitna konverzija tipova u logički tip, nakon toga se izvrši proracun sa operandom || nad dobijenim logičkim tipovima:

Rešenje ovog uslova je TRUE, nakon čega operator || prema pravilu vraća vrednost prvog operanda (a to je “foo”).

Više o operatorima pogledajte u članku “JavaScript operatori”

Striktno poredjenje objekata?

Poredjenjenje objekata sa “===” operatorom uvek vraća FALSE jer se objekti prosledjuju prema referenci u memoriji. Na sledećem primeru se striktno porede dva objekta sa istim vrednostima, medjutim pošto su te vrednosti sačuvane na dva različita mesta u memoriji, poredjenje je uvek FALSE:

Pri dodeljivanju jednog objekta nekoj promenjivoj, prosledjuje se referenca na mesto gde je sačuvan objekat u memoriji. Objekti (u to spadaju i nizovi) se čuvaju u tipu memorije zvanom “heap”.

Ipak treba znati da naredni kod vraća TRUE, jer oba ukazuju na isto mesto u memeriji:

Više o podacima sa referentnim vrednostima pogledajte u članku “Tipovi podataka u JavaScriptu”

Type of Null!?

Jedna od neobičnih i čudnih stvari vezanih za sintaksu jezika je činjenica da je null tipa object!!!

Kontrola postojanja objekta je problematičan task, jer kondicioni uslov obj !== “undefined” daje lažno pozitivne rezultate za null:

Iz prethodnog razloga se najčešće koristi metoda typeof koja vraća string sa kojim definiše tip podataka.

Medjutim prethodna provera nije dovoljna jer objekat može imati dodeljenu vrednost null:

Ali ni prethodni kod nije dovoljno dobar jer ukoliko objekat undefined onda nije null, stoga je bolje da se prvo pita da li je različit od undefined:

Ugradjeni objekti koji liče na primitive

U JavaScript-u pored tipa “object” postoje još 6 “prostih primarnih” tipova koji nisu objekat: string, number, boolean, undefined, null i symbol. Ali često zbrku pravi činjenica da pored primitivnih tipova postoje i ugradjeni objekti čija su imena ista kao kod prostih primitiva stim što nazivi počinju sa velikim slovom: String, Number, Boolean, Function, Array.

Često dolazi do implicitne konverzije prostog primarnog tipa u njegov “parnjak” objekat pa dolazi do neočekivanih rezultata pri striktnom poredjenju. Na sledećem primeru je prikazano da striktno poredjenje vraća false:

U prethodnom primeru snippet-i vraćaju FALSE, jer se pri korišćenju rezervisane reči new se kreira novi objekat.

Više o ovome pročitajte u članku “Tipovi podataka u JavaScriptu” pod sekcijom “Da li je sve u JavaScript-u objekat?”

Problemi sa “prikrivenom” konverzijom tipova

Implicitna konverzija Objekta u string

Ukoliko želimo da ištampamo u konzoli neki objekat zajedno sa pratećim tekstom, dobijamo neočekivani rezultat:

Korišćenje operatora + sa operandima koji nisu brojevi nije sabiranje već konkatenacija string-ova. Konkatenacija je privilegija sting-ova, stoga rezultat iz prethodnog primera je posledica implicitne (prikrivene) konverzije objekta u string.

Iz prethodnog se zaključuje da svaka konverzija objekta u string vraća “[object Object]”

Rešenje:

Da bi se objekat “lepo” ištampao potrebno je da prvo JavaScript objekat prebacimo u string sa JSON.stringify() metodom:

NAPOMENA:
Ukoliko na prvom snippet-u umesto + stavimo zarez dobićemo željeni rezultat, jer operator zarez izvršava postavljeni task za sve zarezom odvojene parametre a ne kastuje objekat u string:

Implicitna konverzija niz-a u string

Kada zbog konkatenacije dolazi do implicitne konverzije niza u string, dobijaju malo drugačiji rezultati:

Više o ovome pročitajte u članku Konverzija tipova u JavaScript-u”

Striktna komparacija unutar Switch() izraza

Prethodni snippet nikada neće aktivirati alert, jer switch izraz traži da se podudara i tip podatka. Ali ukoliko prvo kastujemo promenjivu u string naredni snippet će davati ispravne rezultate.

Specifičnost metode replace()

Potrebno je znati da metoda replace() utiče samo na prvi element koji pronadje:

Stoga ukoliko želimo da primenimo zamenu string-a na sve tražene elemente, potrebno je da koristimo regular expression i to globalno:

Problem numerisanja meseci u Date objektu

Meseci u JS objektu “date” počinje da se numerišu od nule, za razliku od godjine i dana koji počinju da se nabrajaju od broja jedan.

Definisanje niza sa jednim argumentom

Ukoliko se pri deklarisanju niza stavi samo jedna vrednost, JS smatra da je to “lenght” niza. Zbog toga JavaScript pravi niz takve dužine čiji članovi još nisu definisani.

Problem sortiranja niza brojeva sa metodom sort()

Metoda sort() je inicijalno namenjena za sortiranje niza stringova, stoga pri sortiranju niza brojeva daje pogrešne rezultate. Za korišćenje sa brojevima je potrebno dodati funkciju poredjenja.

Rešenje

Za korišćenje sa brojevima je potrebno dodati funkciju poredjenja.

Argumenti funkcije

Lenght funkcije

Svojstvo lenght kod funkcija vraća broj definisanih argumenata (ovde su 2kom: a,b) u funkciji (ne prosledjenjih ili korišćenih argumenata).

Specifičnost “arguments” objekta

Arguments je “Array like” objekat stoga za razliku od “običnog” objekta ima ima svojstvo lenght. Operator delete se koristi da briše svojstva objekta, u narednom primeru zelimo da obrišemo prvo svojstvo objekta arguments.

Medjutim iako je operator obrisao prvo svojstvo objekta “arguments” to ne utiče na vrednost koju vraća metoda lenght(), jer metoda lenght vraća broj prosledjenih argumenata funkciji (u ovom slučaju su 3 kom: 5, 7, 9).

Zaklanjanje promenjive (shadowing)

Činjenica da je promenjiva iz spoljnog scope-a uvek dostupna kodu unutar unutrašnjeg scope, može da nas “zavede” da pomislimo da će se štampati “pocetna je 1000”. Medjutim ovde dolazi do zaklanjanja promenjive (eng. variable shadowing). Kada JavaScript engine traži vrednost neke promenjive on prvo pogleda u najbližoj oblasti definisanosti, a pošto je promenjiva definisana u sklopu funkcije, on je tu i nalazi (dalje i ne traži pa nikada ne dodje do spoljne promenjive). Pored “zaklanjanja promenjive” dešava se i proces hoisting-a promenjive unutar funkcije. Ovakvo rešenje se lako objašnjava u narednom snippet-u, gde je prikazan kod trenutku parsiranja JavaScript-a.

Vrednost “this” u “izvučenoj” metodi objekta

Metoda je samo svojstvo objekta koje ima referencu na neku funkciju. Kada metodu objekta dodelimo nekoj promenjivoj mi smo promenjivoj dodelili referencu na funkciju. Stoga pozivanjem te promenjive mi ne pozivamo metodu objekta nego običnu funkciju iz globalnog domena. Ključna reč THIS unutar funkcije koja se poziva iz globalnog scope prema pravilima za THIS ukazuje na globalni objekat (window).

Primer

Objašnjenje primera

Pošto metoda osoba.punoIme ima referencu na anonimnu funkciju možemo je ubaciti njeno mesto pa prethodni primer sa gledišta this prelazi u sledeći kod:

Iz priloženog se vidi da se poziva funkcija prikazPunogImena(), a ne metoda objekta. Stoga se koristi “podrazumevano pravilo” kada this ukazuje na globalni objekat window koji nema svojstvo window.ime i window.prezime pa vraća undefined, undefined.

Rešenje problema

Ovaj problem se rešava sa bind() metodom, kojom ćemo explicitno povezati this iz funkcije sa objektom “osoba”, nakon čega više nije bitno odakle se funkcija poziva.

Više o ovome pročitajte u članku “This u JavaScript-u”

Problem callback funkcije u loop-u

Neočekivano ponašanje callback funkcije u petlji možemo zahvaliti tome što se ona ne poziva odmah dok petlja vrti, već se poziva uvek sa “zakašnjenjem” kada je petlja već izvrtela i došla do poslednje vrednosti brojača. Iz tog razloga callback funkcija će uvek koristiti poslednju vrednost brojača.

Loop i callback funkcija kod setTimeout() metode

Opis procesa

Pozivanje metode setTimeout dok se vrti petlja
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).
Pozivanje callback funkcije po završetku petlje
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:

Ovaj problem možemo da rešimo ako callback funkciju pozovemo pri svakom loop-u, da bi ona “zgrabila” vrednost promenjive “i” u tom trenutku. Na ovaj način pravimo za svaki prolaz kroz petlju novu funkciju, koja zahvaljujući karakteristikama closure u stanju da “pamti” dodeljenu vrednost promenjive “i” u tom krugu.

I način:

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

II način:

Ovaj način koristi osbinu ES6 naredbe let da ponovo deklariše novu promenjivu “i” u svakoj iteraciji petlje.

III način:

U ovom primeru callback funkciju izdvajamo da bi je u sintaksi setTimeout() funkcije mogli pozvati (invoke) pri svakom prolasku petlje. Closure će zapamtiti tu vrednost u trenutku pozivanja i na nju neće uticati kasnija promena vrednosti promenjive tj. “redeklarisanje promenjive”

Loop i callback funkcija kod 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.

Više o ovome možete pročitati u članku “Javascript Clousure” pod sekcijom “Rešavanje problema sa “this” kod callback funkcije u petlji”