Uvod
Svako ko se malo duže bavi programiranjem, svestan je ogromne popularnosti koju JavaScript kao jezik, uživa u ovom trenutku.
Ovaj jezik, svoj život je započeo kao skriptni jezik za pristup DOM elementima u web browserima (naravno, i danas obavlja navedenu ulogu), ali je tokom vremena - pogotovo u poslednjih šest, sedam godina - prevazišao početne okvire i postao osnova velikog broja krajnje zanimljivih radnih okruženja (pre svih, node.js, pa zatim i Angular, React, Vue.js, Express ...), koja zajedno čine svojevrstan "ekosistem" za razvoj, pre svega web aplikacija, ali i desktop aplikacija, kao i raznovrsnih biblioteka i dodataka.
Kada je u pitanju razvojni put programera koji su savladali osnove JS-a i žele da se usmere na prethodno navedene tehnologije, koje nude velike mogućnosti razvoja individualne programerske veštine, kao i brojne prilike za rad na izazovnim i zanimljivim projektima, postavlja se pitanje: šta su najvažnije tehnike koje prethodno treba savladati.
Ako nas pitate, u pitanju su upravo unapređenja koja su došla sa revizijom jezika ECMAScript 2015 (odnosno ECMAScript 6, ili, još jednostavnije - ES6), za koja ćemo u daljem tekstu koristiti opšteprihvaćeni naziv - ES6 sintaksa.
ES6 sintaksa - najvažniji elementi
Nećemo nabrajati sva unapređenja koja su došla sa revizijom ES6, koja se pojavila 2015. (od kada popularnost JS-a zapravo i beleži najveći napredak) i nećemo previše ulaziti u detalje, ali ćemo napraviti izbor najznačajnijih i, slobodno možemo reći - krajnje neophodnih tehnika, za sve one koji žele da se ozbiljnije pozabave Node.js aplikacijama, ili razvijaju sajtove u nekom od popularnih "framework-a", kao što su React, Angular, Vue, ili Express.
U tom smsilu, ovaj članak biće svojevrstan informator za sve programere koji imaju takve namere (a pri tom, već imaju iza sebe neophodno predzanje i određeno iskustvo), ali i svojevrsna rekapitulacija tehnika koje su deo ES6 sintakse, a kojima smo već posvetili detaljne članke, pri čemu će biti reči i o tehnikama koje do sada u člancima nismo spominjali (moduli, destrukturiranje elemenata).
Najvažnijim unapređenjima koje je donela ES6 sintaksa, smatramo arrow funkcije, šablonske niske i promise, ali, krenućemo od nečeg osnovnijeg ....
Novi način označavanja promenljivih - let i const
Možemo reći da stari način definisanja promenljivih preko rezervisane reči var
uglavnom (ipak) nije zadavao prevelike probleme programerima, ali, svakako je imao nedostatke.
Pre svega:
- nije bilo moguće definisati konstante
- nije bilo moguće definisati promenljive koje su definisane samo unutar bloka programskog koda (block scope)
Deklaracija podataka preko rezervisanih reči const
i let
, otklanja navedene nedostatke i proširuje mogućnosti:
const
Kada pri deklaracije podatka, koristimo rezervisanu reč const
, deklarisani podatak ima sledeće osobine:
- mora se inicijalizovati odmah pri deklaraciji
- nije moguće naknadno menjati vrednost
- ako se deklaracija pojavi unutar bloka (između vitičastih zagrada), dati blok koda predstavlja opseg definisanosti podatka
Poslednja stavka je bitna i zanimljiva, jer promenljive definisane preko rezervisane reči var
prepoznaju samo globalni opseg i opseg funkcije.
Kada su u pitanju nizovi i objekti, stvari su malo komplikovanije (ali, i dalje razumljive). Kod podataka deklarisanih preko rezervisane reči const
, "konstantna" je referenca na objekat, dok se stanje objekta može menjati, ali, bez naknadnog pozivanja naredbe dodele (recimo, objektima možemo menjati vrednosti polja, nizovima dodavati i uklanjati elemente i slično).
Na sledećoj slici vidimo primere koji pokazuju šta se može, a šta ne može, raditi sa podacima deklarisanim preko rezervisane reči const
:
// Inicijalizacija se mora obaviti u istoj naredbi sa deklaracijom:
const PI = 3.14159265359; // OK
const g; // GREŠKA!
g = 9.81; // GREŠKA!
// Inicijalizovanim nizovima mogu se dodavati i uklanjati elementi,
// ali se referenca na niz ne sme menjati:
const niz = [1, 2, 3, 4]; // OK
niz.push(5); // OK
niz = [2, 3, 7, 13] // GREŠKA!
// Vrednosti polja objekata mogu se menjati, ali se ne sme menjati
// referenca (tj. ne sme se naknadno koristiti naredba dodele):
const osoba = {
ime: "Petar",
prezime: "Milinković"
} // OK
osoba.ime = "Marija" // OK
osoba = {
id: 1,
uid: korisnikX,
status: admin
} // GREŠKA!
// Deklaracija preko rezervisane reči const poštuje
// opseg bloka uokvirenog vitičastim zagradama:
{
const p = "Proba";
console.log(p); // OK
}
console.log(p); // GRESKA!
let
Promenljive deklarisane preko rezervisane reči let, imaju sledeće osobine:
- može im se menjati vrednost naknadno, ali se ne smeju ponovo deklarisati
- vrednost promenljive može se menjati naknadno
- inicijalizacija se ne mora obaviti pri deklaraciji
- ako se deklaracija pojavi unutar bloka (između vitičastih zagrada), dati blok koda predstavlja opseg definisanosti promenljive
Ponovo ćemo pogledati primer koji prikazuje navedeno:
// Inicijalizacija se može, ali i ne mora,
// obaviti u istoj naredbi sa deklaracijom:
let PI; // OK
PI = 3.14159265359; // OK
// Inicijalizovanim nizovima mogu se dodavati i uklanjati elementi,
// a može se koristiti i naredba dodele nakon inicijalizacije:
let niz = [1, 2, 3, 4]; // OK
niz.push(5); // OK
niz = [2, 3, 7, 13] // OK
// Vrednosti polja objekata mogu se menjati, a može se
// naknadno koristiti i naredba dodele:
let osoba = {
ime: "Petar",
prezime: "Milinković"
} // OK
osoba.ime = "Marija" // OK
osoba = {
id: 1,
uid: korisnikX,
status: admin
} // OK
// Deklaracija preko rezervisane reči let poštuje
// opseg bloka uokvirenog vitičastim zagradama:
{
let p = "Proba";
console.log(p); // OK
}
console.log(p); // GRESKA!
Arrow funkcije
Arrow funkcijama, kao specifičној implementaciji lambda izraza u JavaScript-u, posvetili smo veliki deo članka o lambda izrazima, pa se ovoga puta nećemo previše zadržavati na detaljima, ali, ponovimo najvažnije:
u pitanju je način da se imenovana funkcija (koja je, za određene potrebe, možda previše "zvanična"), zapiše na jednostavniji način:
Ako krenemo od funkcije koja vraća veću od dve unete vredosti
function veciOdDva(a, b) {
return (a > b)? a: b;
}
.... izostavićemo rezervisanu reč funciton, kao i naziv funkcije, i potom izmeću parametara i tela funkcije dodati lambda operator =>:
(a, b) => {
return (a > b)? a: b;
}
Budući da funkcija ima samo jednu naredbu (koja pri tom sadrži rezervisanu reč return), možemo funkciju dodatno pojednostaviti:
- izostavićemo vitičaste zagrade
- izostavvićemo rezervisanu reč return
- telo funkcije zapisaćemo u jednom redu
(a, b) => (a > b)? a: b
Sve to jako dobro dođe u povratnim pozivima i pri tom (što i jeste ideja koja stoji iza ovakvog zapisa i njegova glavna svrha), deluje jako elegantno.
Na primer, umesto imenovane funkcije:
function kvadrat(x) {
return x * x;
}
let niz1 = [1, 2, 3, 4, 5];
let niz2 = niz1.map(kvadrat);
.... ili standardne neimenovane funkcije:
let niz1 = [1, 2, 3, 4, 5];
let niz2 = niz1.map(function() {
return x * x;
});
.... koristićemo arrow funkciju:
let niz1 = [1, 2, 3, 4, 5];
let niz2 = niz1.map(x => x * x);
Arrow funkcije (očigledno) predstavljaju veoma efektno rešenje koje je široko rasprostranjeno u standardnim web aplikacijama koje koriste Javascript, kao i Node.js aplikacijama.
Šablonske niske
Šablonske niske (pattern literals) su obrasci za spajanje teksta sa vrednostima promenljivih i izraza koji su definisani između ``
("backtick") znakova.
Na primer, sledeći kod ....
let a = 5, b = 10;
let s = `Zbir brojeva ${a} i ${b} je ${a + b}`
console.log(s);
.... u konzoli će ispisati:
Zbir brojeva 5 i 10 je 15
Ili, malo konkretniji primer (nalik na primerima koji se svakodnevno koriste kroz React, Angular ili Vue.js)
let paketPodataka = {
naslov: "Šablonske niske",
tekst: "Šablonske niske omogućavaju umetanje vrednosti promenljivih i izraza u tekst"
}
function formatiranjeHTMLElementa(paket) {
return
`
<h2>${paket.naslov}</h2>
<p>
${paket.tekst}
</p>`;
console.log(formatiranjeHTMLElementa(paketPodataka));
}
Promisi
Za razliku od lambda / arrow funkcija koje (makar čitaocima sa prethodnim iskustvom), lako možemo predstaviti u nekoliko reči, sa promisima to nije slučaj (ali, pokušaćemo).
Ukratko (i pre svega), u pitanju je način da se lanac funkcija (gde izvršavanje određene funkcije zavisi od rezultata prethodne), umesto na sledeći način ....
setTimeout(() => {
if(!f1()) return new Error("Funkcija f1 nije se izvršila pravilno"):
setTimeout(() => {
if(!f2()) return new Error("Funkcija f2 nije se izvršila pravilno"):
setTimeout(() => {
if(!f3()) return new Error("Funkcija f3 nije se izvršila pravilno"):
setTimeout(() => {
if(!f4()) return new Error("Funkcija f4 nije se izvršila pravilno"):
setTimeout(() => {
if(f5()) {
console.log("Čestitamo, uspeli ste!");
}
else {
console.error("Gre'ota! Niste imali sreće na samom kraju!")
}
}, INTERVAL_5);
}, INTERVAL_4);
}, INTERVAL_3);
}, INTERVAL_2);
}, INTERVAL_1);
.... zapiše mnogo preglednije:
f1()
.then(f2)
.then(f3)
.then(f4)
.then(f5)
.then(rez => krajnjaObrada(rez))
.catch(greska => obradaGreske(greska));
Kada su promisi u pitanju, sa standardom ES7 (iz 2017.) došla je i async / await
sintaksa (pri čemu ćemo biti praktični i pomenuti je, iako očigledno nije deo ES6 standarda):
try {
let rez1 = await f1();
let rez2 = await f2(rez1);
let rez3 = await f3(rez2);
let rez4 = await f4(rez3);
let rez5 = await f5(rez4);
krajnjaObrada(rez5);
}
catch (greska) {
obradaGreske(greska);
}
Rad sa modulima
U velikim projektima, kada se javi potreba za podelom koda na manje celine, možemo kod podeliti u zasebne datoteke, a potom, preko rezervisane reči export
, omogućiti da elovi koda iz određene JS datoteke postanu dostupni ostalim datotekama.
Na primer, funkcije iz datoteke funkcije.js
....
export function Obim(a, b) {
return 2 * a + 2 * b;
}
export function Povrsina(a, b) {
return a * b;
}
.... možemo importovati i koristiti u drugoj datoteci:
import { Obim, Povrsina} from './funkcije';
console.log(Obim(5, 10)); // 30
console.log(Povrsina(5, 10)); // 50
Destrukturiranje elemenata
Destrukturiranje elemenata dozvoljava veoma zanimljiv način pristup elementima koji se nalaze unutar objekata i nizova.
Na primer, ako je objekat definisan na sledeći način:
let osoba = {
ime: "Petar",
prezime: "Petrović"
}
... možemo ga (po potrebi) destrukturirati:
let { ime, prezime } = osoba;
console.log(ime); // Petar
console.log(prezime); // Petrović
Ili, u slučaju niza:
let niz = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let {prvi, dugi, treci, ...ostali} = niz;
console.log(prvi); // 1
console.log(drugi); // 2
console.log(treci); // 3
console.log(ostali); // [4, 5, 6, 7, 8, 9, 10]
Klase
Iako smo se klasama u Javascript-u već ranije bavili, spomenućemo ih ukratko i ovde, jer su uvedene upravo sa revizijom ES6.
Klasa definiše šablon za kreiranje objekta:
class Osoba
{
constructor(ime, prezime)
{
this.ime = ime;
this.prezime = prezime;
}
}
.... koji se potom može instancirati:
let osoba = new Osoba("Petar", "Milinković");
Podrazumevane vrednosti parametara funkcija
Počevši od revizije ES6, Javascript dozvoljava korišćenje podrazumevanih vrednosti za parametre funkcija (prepoznaju se po naredbi dodele):
function zbir(a, b = 15) {
return a + b;
}
console.log(zbir(10, 12)); // 22
console.log(zbir(10)); // 25
Funkcije sa proizvoljnim brojem parametara
Ako funkciju deklarišemo na sledeći način:
function suma(....parametri) {
let s = 0;
for(let p of parametri) {
s += p;
}
return p;
}
.... moći ćemo da joj uputimo sledeći poziv:
console.log(sum(1, 2, 19, 74, 211)); // 307
.... pri čemu vidimo da se predati argumenti tretiraju kao niz
Nove matematičke funkcije
Sa standardnom ES6, došle su i nove matematičke funkcije:
Math.cbrt(8); // Kubni koren - Math.cbrt(8) == 2
Math.sign(-10); // Znak broja - Math.sign(-10) == -1
Math.trunc(4.51); // Znak broja - Math.sign(4.51) == 4
Math.log2(32); // Logaritam za osnovu 2 - Math.log2(32) == 5
Math.log10(100); // Logaritam za osnovu 10 - Math.sign(100) == 2
Eksponencijalni operator
Za kraj, jedna sitnica, koja, iako ne predstavlja preveliko unapređenje, svakako dobro dođe.
Umesto da stepenovanje zapišemo na sledeći način ....
let a = 2 * 2 * 2 * 2;
.... naredbu koja računa četvrti stepen broja dva, sada možemo zapisati mnogo jednostavnije:
let a = 2 ** 4; // 16
Zaključak
Kao što smo već rekli, ovaj članak bio je kratka rekapitulacija i "usputna stanica" za one koji su odlučili da savladaju React, Vue ili Angular (ili neku od drugih popularnih i široko rasprostranjenih tehnologija), ali, sve tehnike o kojima smo pisali možemo posmatrati i nezavisno, kao veoma zanimljivu pojavu i deo jezika koji je često osporavan, ali je sa vremenom postajao sve bolji i bolji.
Posle upoznavanja sa ES6 sintaksom, otvaraju se brojni putevi (mi smo nabrojali samo neke, one najpopularnije) i, koji god da izaberete, želimo vam puno sreće u daljem razvoju!