You are on page 1of 6

JavaScript - dodatak

Vidljivost variabli
Za razliku od programskih jezika sa kojim ste se do sada susretali u nastavi
JavaScript ima nešto drugačiji pristup definisanju vidljivosti variabli. Kod programskih
jezika poput C ili C++ variable su vidljive na nivou bloka (blok je jedna ili više linija
koda koje se nalaze u vitičastim zagradama), dok su kod JavaScripta variable vidljive
na nivou funkcije u kojoj su definisane. I u JavaScript programskom jeziku postoji
vidljivost na nivou bloka, a o tome više nešto kasnije.

Posmatrajte sljedeći kod:

for(var i =1;i<5;i++){}
console.log(i);

Rezultat ispisa u konzoli će biti 5 jer je vidljivost variable i na nivou funkcije u kojoj se
deklaracija variable nalazi. U ovom slučaju vidljivost je globalna jer se for petlja ne
nalazi niti u jednoj funkciji. Tako da se nakon izvršavanja svih iteracija petlje variabla i
uvećava do 5 nakon čega se vrijednost ispisuje.

U drugom primjeru vidimo kako ovo svojstvo može imati zbunjujuće rezultate:

for(var i =1;i<5;i++){
if(i==3){
var fja= function(){
console.log(i);
}
}
}
fja();

Kao rezultat ispisa neko bi očekivao 3. Postoji uslov koji je ispravno napisan i koji
kaže u slučaju kada variabla i ima vrijednost 3 definiši funkciju fja koja će ispisivati i.
Postavlja se pitanje koja je onda vrijednost variable i? Da bi nam bilo lakše shvatiti
isti kod možemo prepisati na način da izvučemo sve deklaracije variabli na vrh, jer se
vidljivost variabli veže za funkciju u kojoj se nalaze, a kako se variabla i i funkcija fja
ne nalaze niti u jednoj funkciji direktno njihova vidljivost je globalna.

Isti kod prepisan na način kako bi vidljivost variabli bila očita:

var i;
var fja;
for(i =1;i<5;i++){
if(i==3){
fja= function(){
console.log(i);
}
}
}
fja();

Funkcija se deklariše na početku koda, definiše se šta ona radi unutar for petlje kada
je i=3, a poziva se nakon završetka petlje. Kako je i globlana variabla u trenutku
pozivanja ona ima vrijednost 5, zbog čega se i ispisuje ta vrijednost.

Postavlja se pitanje kako učiniti da se zaista ispiše broj 3. Kako smo rekli da je
vidljivost variable na nivou funkcije, onda je logično dodati funkciju koja će uzeti
vrijednost variable i u trenutku kada je ona 3, snimiti je u svoj lokalni nivo vidljivosti:

var i;
var fja;
for(i =1;i<5;i++){
if(i==3){
function vidljivost(x){
fja= function(){
console.log(x);
}
}
vidljivost(i);
}
}
fja();

U trenutku kada i ima vrijednost 3 definiše se funkcija vidljivost, koja kao parametar
prima neku vrijednost x, a u tijelu funkcije se definiše funkcija fja koja će ispisati
vrijednost x. Kako bi x poprimio vrijednost variable i, odmah nakon toga se poziva
funkcija vidljivost i prosljeđuje joj se i. Na ovaj način x je zaštićeno od promjena
izvana jer je nivo njegove vidljivosti samo unutar tijela funkcije vidljivost. Rezultat
ispisa ovog koda je očekivano 3.

Posmatrajmo sada sljedeći isječak koda:

function nivoA(){
var x="X";
var y="Y";
function nivoB1(){
var y="B";
console.log("nivoB1: "+x+" "+y);
}
nivoB1();
console.log("nivoA: "+x+" "+y);
function nivoB2(){
var y="C";
console.log("nivoB2: "+x+" "+y);
var x;
}
nivoB2();
console.log("nivoA: "+x+" "+y);
function nivoB3(){
y="D";
x="E";
console.log("nivoB3: "+x+" "+y);
var x;
}
nivoB3();
console.log("nivoA: "+x+" "+y);
}
nivoA();

U prethodnom primjeru imamo funkciju koja se zove nivoA i koja sadrži dvije variable
x i y i tri funkcije: nivoB1, nivoB2 i nivoB3. Kako smo rekli da je vidljivost variabli
definisana na nivou funkcije u kojoj se nalazi deklaracija tako je vidljivost variabli x i y
dostupna svim funkcijama koje se nalaze unutar funkcije nivoA.
U funkciji nivoB1 smo deklarisali lokalnu variabli y i njena vidljivost je unutar tijela
funkcije nivoB1. Kompajler će tako ukoliko bilo gdje unutar funkcije nivoB1
upotrijebite variablu y prvo pogledati da li se deklaracija te variable nalazi u funkciji i
ako se nalazi kompajler neće tražiti dalje, međutim ukoliko se deklaracija variable ne
nalazi u trenutnoj funkciji kompajler će gledati variable u prvoj funkciji u kojoj je
deklarisana trenutna funkcija. U našem slučaju variabla x se ne nalazi u trenutnoj
funkciji pa kompajler gleda dalje. Funkcija u kojoj je deklarisana nivoB1 funkcija je
nivoA i ona sadrži deklaraciju variable x, tako da će kompajler iščitati njenu vrijednost
(tj. vrijednost ‘X’)
Imajući na umu ranije rečeno logično je zaključiti da je prvi ispis “nivoB1: X B”.
Drugi ispis je “nivoA: X Y”, vrijednost y variable nije promjenjena jer je funkcija
nivoB1 mijenjala samo svoju lokalnu variablu.
Treći ispis je “nivoB2: undefined C”, variabla x unutar funkcije nivoB2 nije
definisana, ali je deklarisana. Kako smo rekli da možemo smatrati da kompajler sve
deklaracije unutar funkcije pomjeri do ispod linije deklaracije funkcije u kojoj se one
nalaze, funkciju nivoB2 možemo drugačije zapisati kao:

function nivoB2(){
var x,y;
y="C";
console.log("nivoB2: "+x+" "+y);
}

Ovim nije ništa promjenjeno i rezultat izvršavanja ove funkcije je isti kao one iz
prethodnog isječka.
Četvrti ispis je “nivoA: X Y”, jer funkcija nivoB2 nije mjenjala variable iz funkcije
nivoA.
Peti ispis je “nivoB3: E D”.
Šesti ispis je “nivoA: X D”, funkcija nivoB3 u svom lokalnom scope-u nije imala
variablu y pa je kompajler trebao pristupiti funkciji nivoA, gdje je pronašao variablu y,
a čiji sadržaj je promjenjen u prvoj liniji funkcije nivoB3.

Block scope vidljivost variabli


Sa ECMAScript 6 verzijom standarda JavaScript-a uveden je i način deklarisanja
variabli koje će imati nivo vidljivosti u okviru bloka koda u kojem su deklarisane (kao i
u svim podblokovima koji se nalaze unutar trenutnog bloka). Ukoliko želite deklarisati
variablu koja će biti vidljiva na nivou bloka koristite let ili const ključne riječi. Za
razliku od deklaracije sa var ključnom rječi let ne dozvoljava ponovno deklarisanje
variable u istom bloku koda. Takođe variable deklarisane sa let se ne vežu za
početak funkcije kao variable sa deklaracijom putem var ključne rječi. Variable
deklarisane sa ključnom riječi const ne mogu se ponovno dodjeljivati, ali se može
mjenjati njihova vrijednost (ako je variabla neki objekat, onda se atributi tog objekta
mogu mjenjati, ali se ne može mjenjati na koji objekat se ta variabla odnosi).
Više o ovome na:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Statements/const

Closure
Upozorenje: Sljedeći dio ne čitajte dok ne shvatite prethodne primjere!

1. function nivoA(){
2. var x="A";
3. function nivoB(){
4. var x="B";
5. function nivoC(){
6. console.log(x);
7. }
8. nivoC();//ispis je B
9. return nivoC;
10. }
11. var preneseniNivoC=nivoB();
12. preneseniNivoC();//ispis je B
13. }
14. nivoA();

Stek poziva je struktura podataka koja vodi računa o tome odakle je određena
funkcija pozvana. Pri pozivanju funkcije na stek se dodaje funkcija iz koje je poziv
pokrenut. U prethodnom primjeru stek bi izgledao otprilike kao:
1. Poziv - 14. nivoA() Stek: nivoA()
2. Poziv - 11. nivoB() Stek: nivoA(), nivoB()
3. Poziv - 8. nivoC() Stek: nivoA(), nivoB(), nivoC()
4. Return iz nivoC Stek: nivoA(), nivoB()
5. Return iz nivoB Stek: nivoA()
6. Poziv - 12. preneseninivoC() Stek: nivoA(), nivoC()

U ovom primjeru imamo tri nivoa vidljivosti: nivo funkcije nivoA, nivo funkcije nivoB i
nivo funkcije nivoC. Definisana je varabla x unutar nivoA i druga variabla x unutar
nivoB. Funkcija nivoC direktno ne vidi variablu x, ali funkcija u kojoj se ona nalazi ima
definisanu variablu sa tim imenom (variabla x u funkciji nivoB). I u prvom pozivu
funkcije nivoC() logično je da je rezultat funkcije ‘B’ (poziv iz linije 8).
Kada se funkcija nivoC pozove iz linije 12 koristeći povratnu vrijednost funkcije nivoB
(nivoB vraća funkciju nivoC) stvari su nešto drugačije. Ukoliko gledamo mjesto
izvršenja i stek poziva ne bi vidjeli variabu x iz funkcije nivoB nego variablu x iz
funkcije nivoA. Kako je ispis ‘B’ zaključujemo da se ne gleda samo mjesto poziva
funkcije, te da JavaScript ima dodatne informacije o variablama. Postoji nešto što se
zove leksički scope i on predstavlja skup svih variabli koji su vidljivi u određenom
djelu koda. On se određuje na osnovu mjesta gdje je variabla definisana. U
prethodnom primjeru postoje tri scope-a koji su ugnježdeni jedan u drugi: scope
funkcije nivoC se nalazi u scope-u funkcije nivoB, a scope funkcije nivoB je u scope-u
funkcije nivoA. Kada kompajler traži vrijednost određene variable prvo gleda u
direktni scope u kojem se nalaza, ukoliko se tražena variabla ne nalazi u trenutnom
scope-u kompajler gleda u scope koji sadrži trenutni i sve tako dok ne dođe do
globalnog scope-a ili ne nađe variablu sa datim imenom. Na ovaj način variable koje
se nalaze u nižim scope-ovima zaklanjaju variable sa istim imenima u višim scope-
ovima.
U drugom slučaju se desilo nešto što se zove closure, closure se dešava kada
funkcija zapamti svoj leksički scope čak i kada se izvršava u drugom scope-u. U
našem slučaju funkcija nivoC je zapamtila scope funkcije nivoB i iskoristila je variablu
x iz tog scope-a iako se funkcija u ovom slučaju izvršila unutar leksičkog scope-a
funkcije nivoA.

Scope i Closure objašnjeni na malo drugačiji način:


http://ryanmorr.com/understanding-scope-and-context-in-javascript/

Postavlja se pitanje kada nam ovo može koristiti?

Ukoliko pravite neki modul koji dijeli nekoliko variabli i koji posjeduje nekoliko metoda
tada je korisno definisati glavnu funkciju modul koja će predstavljati zaštićeni scope i
koja će kao rezultat vratiti objekat koji sadrži sve funkcije datog modula. Na ovaj
način sakrivene variable modula su zaštićene od izmjena izvan modula, te se možete
pouzdati da ćete uvijek čitati variable koje je modul postavio, a ne neki drugi kod koji
koristi modul.

Na primjeru dat ćemo objašnjenje module patterna i kako se on koristi u JavaScript-


u. Napišitimo modul koji sadrži 5 metoda: saberi, oduzmi, sx (skalarni proizvod),
postaviX1 (postavlja prvi operand operacije) i ispisi.

var Operacije =(function(){


var operand1={x:0,y:0};
var saberi = function(operand2){
operand1= {x:operand1.x+operand2.x,y:operand1.y+operand2.y};
}
var oduzmi = function(operand2){
operand1= {x:operand1.x-operand2.x,y:operand1.y-operand2.y};
}
var sx = function(opereand2){
return operand1.x*operand2.x+operand1.y*operand2.y;
}
var postaviX1 = function(X1){
operand1=X1;
}
var ispisi = function(){
console.log("x: "+operand1.x+" , y: "+operand1.y);
}
return {
saberi: saberi,
oduzmi: oduzmi,
sx: sx,
postaviX1: postaviX1,
ispisi: ispisi
}
}());

//primjer korištenja modula


Operacije.postaviX1({x:1,y:0});
Operacije.ispisi();
Operacije.saberi({x:2,y:2});
Operacije.ispisi();

U primjeru je definisan modul Operacije. Za ovaj modul vrijedi to da su sve njegove


variable i funkcije definisane unutar ograničenog scope-a, tj. postoji jedna funkcija
koja okružuje kod modula. Ova funkcija se odmah poziva nakon definisanja i
anonimna je (bez imena). Na ovaj način modul Operacije ima svoj vlasititi
namespace u kojem su variable zaštićene od vanjskih izmjena. Povratni tip ove
funkcije je objekat koji sadrži kao atribute sve funkcije modula koje trebaju biti javne.
Sve funkcije koje su definisane unutar modula, a nisu vraćene kroz objekat su
privatne funkcije tog modula. Ovaj pattern nam omogućava da izbjegnemo konfilkte u
imenovanju variabli.