Uvod
ES5 verzija JavaScript-a nema podršku za module, ali bez obzira na to je moguće držeći se odredjenih principa napraviti aplikacije u maniru modularnog programiranja. Module sa svojim enkapsuliranim kodom kreiramo koristeći “Immediately-Invoked Function Expressions” ili sa konstruktor funkcijom. API modula koji treba da bude javan jednostavno vratimo unutar funkcije sa rezervisanom reči return. Ovo je jednostavan šablon i može se primeniti bilo gde bez dodatnih biblioteka. U okviru jednog fajla je moguće definisati više modula.
Pored navedenih prednosti ovaj pristup ipak ima i svojih mana:
- “zagadjivanje” globalnog domena nazivima modula, mada je body modula skriven korišćenjem funkcija (IIFE) u lokalni domen.
- “ručno” odredjivanje redosleda učitavanja modula a redosled učitavanja modula može biti prilično komplikovan kod velikih i kompleksnih aplikacija.
- nije moguće asihrono učitavanje
Ovi šabloni nisu savršeni i imaju svoje mane ali bez obzira na mane, kod je razumljiviji jer je bolje organizovan.
IIFE – Immediately-Invoked Function Expressions
Definicija
IIFE (izgovara se “ifi”) je function expression koja se izvršava odmah nakon stvaranja. Kreira se tako što “deklaraciju funkcije” obavijemo sa parom zagrada i tako napravimo “function expression”. Razlog za transformaciju “deklarisane funkcije” u “function expression”, je taj što deklarisana funkcija ne može biti odmah pozvana u istoj naredbi dok function expression može.
1 |
( function(){} ); |
Nakon pravljenja function expression u istoj naredbi dodamo još jedan par zagrada i tako odmah pozovemo funkciju.
1 |
( function(){} )(); |
Privatnost podataka unutar IIFE
IIFE se koristi za modularni patern programiranja jer sav kod unutar ovakve funkcije ostaje privatan, što se vidi u narednom primeru.
1 2 3 4 5 6 7 8 |
(function(){ var ime = "Dragoljub"; var jezik = "JavaScript"; var programer = ime + " je " + jezik + " programer"; console.log(programer); })(); // Vraća: "Dragoljub je JavaScript programer" console.log(programer); // Vraća: Error programer is not defined |
Prosledjivanje promenjivih u IIFE
Globalne promenjive se mogu proslediti unutar funkcije, pri pozivanju funkcije kao argument te funkcije:
1 2 3 4 5 6 |
(function(ime, jezik){ var ime = ime; var jezik = jezik; var programer = ime + " je " + jezik + " programer"; console.log(programer); })("Dragoljub", "JavaScript"); // Vraća: "Dragoljub je JavaScript programer" |
jQuery biblioteka takodje koristi ovaj pristup da bi prosledila promenjivu:
1 2 3 |
(function ($) { // Sada je kodu dostupna globalna promenjva jQuery kao $ }(jQuery)); |
Vraćanje podataka
Sa IIFE možemo da izaberemo koje podatke želimo da zadržimo kao privatne a koje podatke želimo da objavimo. Javno dostupni će biti oni delovi koda koji se vrate sa rezervisanom reči return. Pomenutim načinom možemo u globalni namespace proslediti sve tipove podataka. U sledećem primeru vraćamo promenjivu a samim tim je činimo je globalno vidljivom:
1 2 3 4 5 6 |
var oceneIspita = (function (nizOcena) { var ocene = nizOcena; return ocene; })([9, 5, 8, 9, 5, 7]); console.log(oceneIspita); //Vraća: [9, 5, 8, 9, 5, 7] |
možemo vratiti funkciju
1 2 3 4 5 6 7 8 9 10 11 12 |
var proracunIspita = (function (nizOcena) { var ocene = nizOcena; function prosek () { var total = ocene.reduce(function(accumulator, item) { return accumulator + item; }, 0); return'Tvoj prosek je ' + total / ocene.length + '.'; } return prosek; })([9, 5, 8, 9, 5, 7]); console.log(proracunIspita()); //Vraća: 'Tvoj prosek je 7.166666666666667.' |
Ili možemo vratiti ceo objekat
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var proracunIspita = (function (nizOcena) { var ocene = nizOcena; var objekat = {}; objekat.prosek = function() { var total = ocene.reduce(function(accumulator, item) { return accumulator + item; }, 0); return'Tvoj prosek je ' + total / ocene.length + '.'; } return objekat })([9, 5, 8, 9, 5, 7]); console.log(proracunIspita.prosek()); //Vraća: 'Tvoj prosek je 7.166666666666667.' |
Revealing module pattern
Najčešće nije potrebno vratit ceo objekat već je dovoljno otkriti javnosti samo neke delove koda, što se primenjuje kroz “Revealing module pattern”. Sa ovim principom možemo da izberemo koje metode želimo da budu privatne a koje da budu javne i dostupne ostalim modulima.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var proracunIspita = (function (nizOcena) { //PUBLIC PROMENJIVA var ocene = nizOcena; //PRIVATNA METODA: function _brojPadova () { var padovi = ocene.filter(function(item) { return item < 6; }); return 'Pali ste ' + padovi.length + ' puta.'; } //PUBLIC METODA: function prosek () { var total = ocene.reduce(function(accumulator, item) { return accumulator + item; }, 0); return'Tvoj prosek je ' + total / ocene.length + '.'; } //IZBOR PUBLIC METODA return { ocene:ocene, prosek: prosek } })([9, 5, 8, 9, 5, 7]); console.log(proracunIspita.ocene); // [9, 5, 8, 9, 5, 7] console.log(proracunIspita.prosek()); // 'Tvoj prosek je 7.166666666666667.' console.log(proracunIspita._brojPadova()); // Vraća: error "proracunIspita.brojPadova is not a function" |
IIFE + Singleton šablon
Opšte
U softverskom inžinjerstvu singleton (srp. unikatni) šablon programiranja je zasnovan na principu da svaki objekat (klasa) ima SAMO JEDNU INSTANCU, uz uslov da pristup toj instanci bude globalno dostupan. U JavaScript-u je to moguće dodeljivanjem IIFE globalnoj promenjivoj. U modulima se “otkrivaju” metode koje želimo da budu globalno dostupne prateći “Revealing module pattern”. Sa je IIFE se pravi lokalni scope i sprečava se zagadjivanje globalnog domena, ali naziv svakog modula postaje deo globalnog domena i može doći u koliziju sa nekom externom bibliotekom ukoliko njena promenjiva ima isto ime.
Praktična primena u aplikaciji
index.html
Ovaj fajl čini osnovu aplikacije i služi kao view za prikupljanje podataka od korisnika i vraćanje razultata. Obratiti pažnju na deo koji je zadužen za učitavnje modula, jer redosled učitavanja modula zavisi od funkcionalnosti aplikacije. Ovo je najteži deo za programera naručito kod velikih i kompleksnih aplikacija. U ovom primeru se prvo učitava “unos.js” jer se njegove metode koriste i u okviru “proracun.js” i “app.js”, dok se skripta “proracun.js” učitava odmah za njom ali pre “app.js” jer je njena metoda sastavni deo “app.js”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<html> <head lang="en"> <meta charset="UTF-8"> <title>Modularna aplikacija</title> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <span class="navbar-brand">MODULARNA APLIKACIJA</span> </div> </div> </nav> <div class="form-horizontal" id="nameform"> <!-- UNOS PODATAKA: --> <div class="form-group"> <label for="entry" class="col-sm-2 control-label">Ulazni podaci</label> <div class="col-sm-2"> <input type="text" class="form-control" id="entry" size="20" placeholder="Unesite podatke" /> </div> </div> <!-- DUGME ZA STARTOVANJE APLIKACIJE (PRORAČUN): --> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button class="btn btn-success" id="calculate">Proračun</button> </div> </div> </div> <!-- SEKCIJA ZA VRAĆANJE REZULTATA: --> <div class="col-sm-10" id="scores"> <h3>REZULTAT:</h3> <div id="rezultat"> <p>Proačunati iznos je: <span></span></p> </div> </div> <!-- SEKCIJA ZA UČITAVANJE MODULA --> <script src="js/unos.js" type="text/javascript"></script> <script src="js/proracun.js" type="text/javascript"></script> <script src="js/app.js" type="text/javascript"></script> </body> </html> |
unos.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var unos = function() { // PRIVATNA PROMENJIVA var unesenPodatak = ''; // FUNKCIJA KOJA PRIHVATA UNOS function setPodatak(noviPodatak) { unesenPodatak = noviPodatak; } // FUNKCIJA KOJA PROSEDJUJE UNOS function getpodatak() { return unesenPodatak; } // JAVNO OTKRIVANJE METODA return { setPodatak: setPodatak, getpodatak: getpodatak }; }(); |
proracun.js
1 2 3 4 5 6 7 8 9 10 11 |
var proracun = function() { function calculateScore() { // POZIV METODE IZ unos.js unos.getPodatak(); // ovde ide deo koda vezan za PRORACUN } // JAVNO OTKRIVANJE METODE return { calculateScore: calculateScore, }; }(); |
app.js
1 2 3 4 5 6 7 8 9 10 11 |
(function() { // "click handler" ZA UNOS PODATAKA (poziva funkciju iz modula unos.js) document.getElementById('entry').addEventListener('change', function() { unos.setPodatak(document.getElementById('entry').value); }); // "click handler" ZA STARTOVANJE PRORAČUNA (poziva funkciju iz proracun.js) document.getElementById('calculate').addEventListener('click', function() { proracun.calculateScore(); }); })(); |
Konstruktor šablon
Opšte
Konstruktor šablon omogućava pravljenje više instanci na osnovu konstruktorske funkcije. Izmene koda u odnosu na prethodni šablon su male. Telo funkcije ostaje nepromenjeno dok se prilagodjavaju samo sledeće stvari:
- Naziv promenjive kojoj se dodeljuje funkcija treba da počinje sa velikim početnim slovom, da bi se zadovoljila konvencija
- IIFE se transformiše u običnu funkciju, brisanjem zagrada koje odmah pozivaju funkciju jer je standardna konstruktorska funkcija planirana da se poziva samo sa rezervisanom reči “new”
- Unutar modula koji potražuje metodu iz drugog modula potrebno je instancirati novi objekat, da bi bila dostupna njegova metoda.
Praktična primena u aplikaciji
unos.js
Ovaj modul ne koristi metode iz drugih modula, pa su izmene vezane samo za ukidanje zagrada za pozivanje funkcije.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var Unos = function() { // PRIVATNA PROMENJIVA var unesenPodatak = ''; // FUNKCIJA KOJA PRIHVATA UNOS function setPodatak(noviPodatak) { unesenPodatak = noviPodatak; } // FUNKCIJA KOJA PROSEDJUJE UNOS function getpodatak() { return unesenPodatak; } // JAVNO OTKRIVANJE METODA return { setPodatak: setPodatak, getpodatak: getpodatak }; }; |
proracun.js
Unutar ovog modula je potrebno da se instancira novi “Unos” objekat.
1 2 3 4 5 6 7 8 9 10 11 12 |
var Proracun = function() { function calculateScore() { // POZIV METODE IZ unos.js var unos = new Unos(); // DODATI DEO KODA unos.getPodatak(); // ovde ide deo koda vezan za PRORACUN } // JAVNO OTKRIVANJE METODE return { calculateScore: calculateScore, }; }; |
app.js
Unutar ovog modula je potrebno da se instancira novi “Unos” i “Proracun” objekat.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(function() { // "click handler" ZA UNOS PODATAKA (poziva funkciju iz modula unos.js) document.getElementById('startGame').addEventListener('click', function() { var unos = new Unos(); //DODATI DEO KODA unos.setName(document.getElementById('entry').value); }); // "click handler" ZA STARTOVANJE PRORAČUNA (poziva funkciju iz proracun.js) document.getElementById('calculate').addEventListener('click', function() { var proracun = new Proracun(); // DODATI DEO KODA proracun.calculateScore(); }); })(); |