JavaScript tipovi podataka
JavaScript spada u slabo tipizirane (loosely typed) jezike. To znači da sam jezik razlikuje tipove, ali da programer ne mora unapred zadavati tipove promenljivama, već ih računar automatski prepoznaje.
Takođe, u JavaScriptu je dozvoljeno da ista promenljiva tokom programa dobija vrednosti različitih tipova. Odvajanje memorije za svaku promenljivu se ne vrši pre izvršavanja programa (tj. tokom kompajliranja), već tokom izvršavanja svaki put kada promenljivoj dodelimo novu vrednost. Programski jezici koji dozvoljavaju ovakvu praksu su dinamički tipizirani (dynamic typed), ili kako smo rekli na početku - slabo tipizirani jezici.
Sada, kad smo to razjasnili, da se bacimo na posao! Osnovni ("primitivni") tipovi koje JavaScript prepoznaje su:
- Numerički (Number)
- Tekstualni (String)
- Logički (Boolean)
- Simbolički (Symbol)
Tu su zatim i dve "nedefinisane" vrednosti, koje su toliko specifične da predstavljaju zasebne tipove:
- Nedefinisana vrednost (undefined)
- Nulta vrednost (null)
Sve ostalo što može da se nađe u JavaScript programu, spada u:
- Objekat (object)
Osnovne (primitivne) vrednosti
Primitivne vrednosti su nepromenljive (imutabilne). Ovde nemojte brkati nepromenljivost vrednosti sa promenljivama. Normalno, promenljiva X može u jednom trenutku imati npr. vrednost 5 a u drugom 12. Međutim, ono što nepromenljivost znači je da ne možemo naterati sam broj 5 da postane nešto drugo. I dok ovo izgleda sasvim normalno i logično, drugačija je situacija sa tekstualnim podacima, odnosno stringovima.
Programeri koji su ranije radili u jeziku C ili Pascalu, u stvari jesu navikli da stringovi budu promenljivi. U tim jezicima je sasvim moguće pristupati i menjati znakove jednog istog stringa. Ne zaboravite - u tim jezicima se stringovi tretiraju kao nizovi znakova, dok ih JavaScript posmatra kao jedinstvene vrednosti. Tako je u JavaScriptu nemoguće promeniti npr. treći znak stringa i "Fractal" pretvoriti u "Fructal".
Ono što jeste moguće je napraviti novi string i dodeliti ga istoj promenljivoj. Krajnji rezultat je isti, ali postignut na sasvim drugačiji način.
Numerički tip
Tradicionalno, počinjemo sa numeričkim tipom. Ako imate iskustva u nekom "klasičnom" programskom jeziku kao što su Pascal, C ili čak Java, iznenadiće vas da u JavaScriptu nema celih brojeva. Svi brojevi se beleže u pokretnom zarezu (64-bitni zapis dvostruke preciznosti - tzv. double). Suštinski to znači da u JavaScriptu imamo decimalne brojeve u rangu između -(253-1) i 253-1. U memoriji, to su 52 bita za mantisu, 11 bitova za eksponenet i 1 bit za znak broja. Ono što je nama značajno je da to u stvari predstavlja broj sa oko 16 preciznih cifara.
Pored trika opisanog u okviru, postoje i "legalni" celi brojevi, ali samo u obliku nizova specijalne namene, tj. tipiziranih nizova (TypedArray). Na primer, ako bismo želeli da radimo sa nizom 32-bitnih neoznačenih celih brojeva (takve vrednosti se koriste recimo za pamćenje piksela neke slike ili canvas objekta) koristili bismo poseban tip niza - Uint32Array. Za neke druge potrebe imamo i nizove 8-bitnih, 16-bitnih celobrojnih označenih ili neoznačenih brojeva, decimalne brojeve jednostruke ili dvostruke preciznosti i sl.
Numerički literali se beleže ciframa od 0 do 9, sa vodećim minusom za negativne brojeve, i decimalnom tačkom ako imamo decimale.
42
-231
3.1415
Takođe, moguće je zadati i brojeve u binarnom, oktalnom i heksadekadnom sistemu:
- binarni - brojevi se sastoje samo iz cifara 0 i 1, i počinju sa 0b ili 0B ("nula-B")
- oktalni - brojevi se sastoje iz cifara od 0 do 7, i počinju sa 0 ("nula" - po starom standardu) ili 0o ("nula-O" - po standardu ES2015)
- heksadekadni - brojevi se sastoje iz cifara od 0 do F, i počinju sa 0x ili 0X ("nula-X")
0b01101011 // broj 107 (binarno)
053 // broj 43 (oktalno)
0o53 // broj 43 (oktalno - po novom standardu)
081 // broj 81 - ako su cifre iznad 7, gleda se kao dekadni
0x1f // broj 31 (heksadekadno)
Inače, u JavaScriptu postoje i neke specijalne numeričke vrednosti:
- +0 i -0 - da, i ovo je "stvar" u JavaScriptu. "Pozitivna nula" je isto što i obična vrednost 0, a u većini slučajeva nećemo primetiti neku razliku ako koristimo "negativnu nulu", osim što pri deljenju sa nulom možemo dobiti "plus beskonačno" ili "minus beskonačno".
- Infinity i -Infinity - "plus beskonačno" i "minus beskonačno". Ove vrednosti se u matematičkim izrazima ponašaju onako kako smo navikli u matematici. Npr. deljenje nekog broja sa Infinity kao rezultat vraća nulu.
- NaN - poseban slučaj broja je i Not a Number, odnosno vrednost koju dobijamo kao rezultat neke nemoguće matematičke operacije (koren od -1, množenje teksta i broja, pokušaj da pretvorimo tekst u broj i sl).
Provera NaN
Najpre da elimnišemo "naivnu" proveru. Recimo da želimo da proverimo vrednost promenljive x - ako postavimo uslov if (!x)..., bićemo u problemu ako vrednost bude npr. 0, što je legitimna numerička vrednost, ali se računa kao false. Isto tako, neke druge vrednosti, koje nisu NaN takođe bivaju računate kao false - undefined i null, prazan string, pa i sam false.
if (!x) // radi ako je x NaN, ali i ako je 0, false, null, undefined...
Pošto NaN nije jednak "sam sebi", ne možemo pitati da li je if (x === NaN)..., jer čak i da jeste NaN, dobićemo netačan rezultat. Sa druge strane, ovu specifičnost možemo "obrnuti" u svoju korist i postaviti uslov if (x !== x).... To znači da proveravamo da li možda x nije jednak sam sebi. Pošto se to dešava samo za NaN, ovakav uslov nije loš način da proverimo da li x ima vrednost NaN. Međutim, šta ako se u budućnosti u JavaScriptu pojavi još neka vrednost koja nije jednaka sama sebi?
if (x === NaN) // ne radi čak i kada x JESTE NaN
if (x !== x) // ovo radi baš kada je x NaN!
Jedna od "legalnih" mogućnosti je korišćenje funkcije isNaN(). Ovakav uslov bi glasio if (isNaN(x))... što nije loše, ali vodite računa kako radi ova funkcija - zadatu vrednost pokušava da pretvori u numerik i ako ne uspe, tj. ako bi se pri tom dobilo NaN, rezultat će biti pozitivan. To znači da bi neki običan "string", ni kriv ni dužan bio proglašen za NaN.
if (isNaN(x)) // radi kada je x NaN, ali i ako je x npr. "Pera"
if (Number.isNaN(x)) // radi samo kada je x baš NaN
Srećom i objekat Number ima metod isNaN(), koji drugačije funkcioniše, tj. vraća vrednost true samo ako zadata vrednost zaista jeste NaN. Dakle, preporučeno je da koristimo if (Number.isNaN(x))... kako bismo proverili da li x ima vrednost NaN.
Još neke specijalne numeričke vrednosti su definisane u objektu Number. Ovde ćemo ih samo pomenuti:
- Number.POSITIVE_INFINITY - isto kao Infinity
- Number.NEGATIVE_INFINITY - isto kao -Infinity
- Number.MAX_VALUE - najveći mogući broj u JavaScriptu
- Number.MIN_VALUE - najmanji mogući broj u JavaScriptu
- Number.MAX_SAFE_INTEGER - najveći mogući celi broj za koji su očuvane sve cifre (sećate se kad smo rekli da numerik u JavaScriptu ima oko 16 preciznih cifara? E, to je to. Vrednost je oko 9 kvadriliona.)
- Number.MIN_SAFE_INTEGER - najmanji "sigurni" celi broj u JavaScriptu - očigledno, -9 kvadriliona.
- Number.EPSILON - najmanja moguća razlika između dva broja u JavaScriptu, možemo reći - najmanji pozitivan broj: 2-52
Tekstualni tip
Osim numeričkog, postoji i tekstualni, odnosno String tip. Ovaj tip predstavlja bilo koji tekstualni podatak u našem programu.
Za razliku od klasičnih programskih jezika, stringovi u JavaScriptu su dinamički tj. njihova veličina se ne zadaje unapred, već se menja po potrebi. Stringovi su ranije bili neograničene dužine. Tek je novi standard ECMAScript 2016 uveo ograničenje da string može biti dugačak maksimalno 253 - 1 znakova. Inače, znakovi stringa su 16-bitni UNICODE karakteri. To u praksi znači da bez ikakvih specijalnih zahteva stringovi mogu sadržati znakove svih nacionalnih pisama, a nama je svakako posebno od značaja što bez problema možemo koristiti ćirilicu i latinicu.
Još jedna razlika u odnosu na "klasične" programske jezike kao što su Pascal i C je što u JavaScriptu stringovi nisu niz znakova. Dakle, zaboravite na direktan pristup znakovima stringa:
Pascal/Delphi
s := 'WEBnSTUDY'; // promenljiva s, tipa string, dobija vrednost 'WEBnSTUDY'
znak := s[4]; // ima vrednost 'n' - četvrti znak stringa
s[4] := '&'; // direktno menjamo četvrti znak stringa, postaje 'WEB&STUDY'
U JavaScriptu je svaki string praktično jedan celovit objekat. Možemo izdvojiti znak-po-znak stringa, ali da znate - svaki izdvojeni znak je ponovo string za sebe. Dakle u JavaScriptu ne postoji znakovni tip (ono što je u npr. Pascalu tip char - pojedinačni znak, praktično jedan bajt).
JavaScript
var s = "WEBnSTUDY"; // promenljiva s, tipa string, dobija vrednost 'WEBnSTUDY'
var z1 = s.charAt[3]; // ima vrednost 'n' - četvrti znak stringa
var z2 = s[3]; // ima vrednost 'n' - četvrti znak stringa (ES5)
s[3] = "X"; // ovo NEĆE RADITI
Notacija za izdvajanje pojedinih znakova stringa samo pomoću uglaste zagrade je "novotarija" uvedena od EcmaScript verzije 5. Inače u JavaScriptu znakovi počinju od 0, tj. prvi znak ima indeks 0, drugi je 1 itd.
JavaScript dolazi sa velikim brojem metoda za rad sa stringovima, koji su ugrađeni u same strngove. Ti metodi pokrivaju biblioteku funkcija za rad sa stringovima kakvu danas ima svaki moderan programski jezik. Treba da znate da ni jedan od tih metoda ne "mutira", tj. ne menja string za koji je zadat, već uvek kreira novi string.
String literali se zadaju unutar navodnika ili apostrofa. Poptuno je svejedno da li zadajemo string na jedan ili drugi način:
"Ovo je string."
'I ovo je string.'
Postoji još jedna vrsta literala i to nisu klasični, već šablonski stringovi (template string). Oni omogućavaju lako kreiranje stringa u više linija i interpolaciju izraza unutar stringa. Zadaju se korišćenjem naopakih apostrofa (backticks):
`Šablonski string sa vrednošću ${x+1}.` // u string ubacuje vrednost x uvećanu za 1
Kao i kod brojeva, i ovde postoji "nulta" vrednost i to je tzv prazan string, tj. string koji nema ništa između navodnika (apostrofa) - čak ni razmak:
"" // prazan string - dva navodnika jedan do drugog
'' // prazan string - dva apostrofa jedan do drugog
Dužina praznog stringa je nula znakova. U logičkim izrazima prazan string se evaluira kao false.
Logički tip
Treći i naizgled najjednostavniji tip je logički tip. Ima samo dve moguće vrednosti:
- true - tačno
- false - netačno
Međutim, kada se vrši logička evaluacija, JavaScript takođe vrši implicitnu konverziju tipova. To suštinski znači da kada se proverava neki uslov, ako vrednost nije logičkog tipa, ona se svodi na logičku vrednost. Evo šta se "računa" kao false:
- 0 - numerička nula se računa kao false, a sve ostale vrednosti (i pozitivni i negativni brojevi) su true
- NaN - Not a Number je isto false
- "" i '' - prazan string se računa kao false, svi ostali stringovi su true
- null - "nulti" objekat je false
- undefined - nedefinisana vrednost je takođe false
Sve ostale vrednosti se računaju kao true. Dakle, bilo kakav string, bilo koji broj (pozitivan ili negativan, čak i Infinity), bilo kakav objekat ili datum. Pazite, čak i "prazni" objekti su i dalje true. Evo nekih vrednosti za koje se pogrešno misli da će biti false:
- "0" - string koji se sastoji od cifre "nula" je true
- " " - string koji se sastoji od jednog razmaka je true
- {} - prazan objekat je true
- [] - prazan niz se ipak računa kao true
Simbolički tip
Simboli su "novotarija" u JavaScriptu. Iako se koristi reč "simbol", ne misli se na neke čudne znakove i hijeroglife. Simboli su prosto oznake, za koje je garantovano da su potpuno jedinstvene i da praktično ne možemo imati dva ista simbola. Takođe, mi u običnom ispisu simbola (npr. u konzoli) ni ne dobijamo njegovu "pravu vrednost", već samo njegov tekstualni opis (a opisi se mogu ponavljati).
Čemu onda simboli služe? Praktična svrha im je da predstavljaju jedinstvene i neponovljive ključeve (svojstva) objekata. U JavaScriptu se često dešava da je objekat pod "uticajem" različitih delova programa, biblioteka ili frameworka. Ne bismo želeli da greškom "pregazimo" neko važno svojstvo objekta ili da neka tuđa funkcija to uradi našem objektu.
Zašto bismo uvodili neka nova svojstva u tuđ objekat? Pa, ponekad nam je potrebno da ubacimo neke dodatne podatke ili da ispratimo šta se dešava sa objektom kada tražimo greške u programu. Tada možemo uvoditi svojstva kao simbole, što nam znači jer se simboli kao ključevi ne budu pozvani pri "običnim" iteracijama kroz svojstva objekta - dakle nema šanse da se pokvari normalno izvršavanje programa. To uključuje čak i serijalizaciju objekta (predstavljanje objekta u JSON tekstualnoj notaciji) - dobijeni rezultat neće uključivati simbolička svojstva objekta.
Simbol se kreira korišćenjem ugrađenog JavaScript objekta Symbol, kome se prosleđuje neki tekstualni opis. Možemo kreirati više simbola sa istim opisom i svaki će biti jedinstven i različit.
var s = Symbol("Opis");
Tako napravljen simbol onda možemo koristiti kao ključ u objektima:
var obj = { // kreiranje objekta preko objektnog literala
[s]: "Pera Perić"
};
ili
obj[s] = "Pera Perić"; // rad sa svojstvom postojećeg objekta
Nedefinisanost i nulti objekat
U JavaScriptu postoje dve "nulte" vrednosti koje su toliko specifične da čak predstavljaju i tipove same za sebe. To su undefined i null.
Vrednost undefined
Ova vrednost opisuje stanje nedefinisanosti. Vrednost undefined imaju:
- promenljive koje pokušavamo da koristimo u izrazima, a nigde ranije nisu deklarisane
- promenljive koje su samo deklarisane sa var ili let, a nikad im nije dodeljena vrednost
- parametri funkcije koji nisu dobili vrednost prilikom poziva funkcije (formalni parametri koji nisu "pokriveni" stvarnim vrednostima)
Provera nedefinisanosti
Vrednost undefined se u logičkoj proveri evaluira kao false. Međutim, uz to će biti i prijavljena greška. Postoji više načina da proverimo da li je neka promenljiva nedefinisana. Najpre, možemo proveriti da li je njena vrednost ekvivalentna undefined:
if (x === undefined) // izbacuje grešku - x nije deklarisano
var y;
if (y === undefined) // prolazi - y nije definisano ali jeste deklarisano
Ako je promenljiva nedeklarisana, "izbaciće" grešku i prekinuti izvršavanje. Međutim, kada je promenljiva samo deklarisana, takođe će biti undefined, ali u ovom slučaju nema greške i izvršiće se if-blok. Pazite, obavezno je koristiti striktnu jednkost ("===" - tri znaka jednakosti) pošto bi sa običnom jednakošću ("==" - dva znaka jednakosti) i neke druge vrednosti, poput null bile izjednačene sa undefined.
Ipak, postoji jedan mali problem. U starijim verzijama JavaScripta, nesmotreni programer je mogao da redefiniše sam undefined, odnonso da mu dodeli neku sasvim drugačiju vrednost. Zbog toga postoji bezbedniji način, a to je korišćenje operatora void koji bilo koji izraz evaluira ali kao vrednost uvek bude "originalni" undefined. Tako da to onda postaje malo sigurniji način u odnosu na gorenavedeno.
if (x === void 0) // izbacuje grešku - x nije deklarisano
var y;
if (y === void 0) // prolazi - y nije definisano ali jeste deklarisano
Srećom, od verzije ECMAScript 5, undefined pstaje read-only, tako da ga postaje nemoguće promeniti. Drugi način je korišćenjem operatora typeof, koji nam služi za proveru tipova:
if (typeof x === "undefined") // uvek prolazi - čak i kada x nije ni deklarisano
Ovo je mnogo "čistiji način - pre svega neće izbaciti grešku ako je promenljiva nedeklarisana - sve "prolazi" bez problema u svakom slučaju.
Vrednost null
Vrednost null je jedina vrednost tipa Null. Ovo je praktično "nulti" objekat. Nemojte mešati ovu vrednost sa "praznim" objektom:
var x = null; // "nulirani" objekat
var y = {}; // "prazan" (naizgled), ali i dalje pravi-pravcati objekat
Za razliku od undefined, null nije zaista nešto "nedefinisano", već pre nedostatak vrednosti. Služi nam da opišemo situaciju kada bi neka promenljiva trebala da bude referenca na objekat, ali iz nekog razloga taj objekat ne postoji.
Provera vrednosti null
Inače, ne brinite - null ćemo mnogo lakše da "hendlujemo" od undefined ili NaN vrednosti. Kada želimo da proverimo da li je nešto null, dovoljno je da to pitamo (koristeći striktnu jednakost):
if (x === null) { ... }
U slučaju da je x zaista null, if-blok će se izvršiti bez problema i bez "ispaljivanja" greške.
Vodite računa da kod provere uvek koristite baz striktnu jednakost, pošto bi u suprotnom null mogao lako da se pobrka sa undefined:
null == undefined; // true - obična jednakost ih poistovećuje
null === undefined; // false - ovako i treba da bude
null == false; // true - null se u logičkim proverama poistovećuje sa false
!null == true; // true - "not null" se poistovećuje sa true
isNaN(1 + null); // false - aritmetička operacija sa null neće kreirati NaN,
// pošto će se null evaluirati u 0
Međutim, ako pokušamo da proveravamo null, operatorom typeof, to će nas uvesti u probleme, pošto iako je null tip za sebe, typeof ga tumači kao object:
if (typeof null === "object") { ... }
Rekli smo da null obično ne "ispaljuje" grešku, ali pošto se od promenljive koja ima vrednost null očekuje da bude objekat, pokušaj pristupa nekom svojstvu objekta koji je u stvari null, rezultuje greškom u programu.
var obj = null; // "nulirani" objekat
obj.x = "vrednost"; // greška!
Iako operator typeof "čita" null kao objekat, ne zaboravite - null je tipa Null, a ne zaista objekat. To znači da null promenljivoj ne možemo dodavati svojstva, kao što to inače radimo sa objektima.
Objekti
Kao što smo rekli na početku, sve ostalo što može da se nađe u JavaScript programu, spada u tip objekta. O objektima će biti mnogo više reči u lekcijama koje tek dolaze. Za sada je dovoljno da znate da je objekat jedna "kompleksna promenljiva" koja u sebi može sadržati više "potpromenljivih", odnosno može se sastojati iz više vrednosti. Ti "elementi" objekta mogu biti čak i drugi objekti (podobjekti), pa čak i funkcije.
Objekat može biti kreiran na više načina, a najjednostavniji je navođenjem objektnog literala:
var x = {}; // "prazan" objekat
var obj = { // objekat sa tri potpromenljive ("svojstva", odnosno "atributa")
ime: "Jovan Jovanović",
visina: 182,
težina: 96
};
Čak su i funkcije u JavaScriptu jedna specijalna vrsta objekta. Kao objekti mogu biti kreirane i primitivne vrednosti - brojevi, odnosno stringovi.
Za sada toliko - objektima ćemo se baviti mnogo više kada dođe vreme.
- MDN web docs, JavaScript data types and data structures
- JavaScript Info, Data Types
- MDN web docs, Number
- MDN web docs, typeof