Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 102

Funkcionalno

programiranje
Školska 2020/21 godina
Zimski semestar
Blok 2
Lekcija 2: Funkcije u programskom
jeziku JavaScript – napredno korišćenje
Sadržaj1
• Streličasta sintaksa
• Prosleđivanje podataka
• Povratni poziv
• Rekurzija
• Sistem tipova i Hindley-Milner signatura tipa
Funkcije: streličasta sintaksa
Šta je streličasta sintaksa
• To je nova, veoma jednostavna i koncizna sintaksa za
kreiranje funkcija koja je često bolja od funkcijskih
izraza.
• Zove se “streličaste funkcije”, jer izgleda ovako:
let func = (arg1, arg2, ...argN) => izraz
• Gornja definicija je isto što i:
let func = function(arg1, arg2, ...argN) {
return izraz;
};
Streličasta sintaksa: konkretan primer
let sum = (a, b) => a + b;

/* Kraći oblik klasične definicije:


let sum = function(a, b) {
return a + b;
};
*/

alert( sum(1, 2) ); // Ispis: 3


Streličasta sintaksa: još kraći
zapis
• Funkcija sa jednim parametrom dozvoljava da se izostave zagrade
oko parametra:
// Recimo let double = function(n) { return n * 2 }
// Može da se piše ovako:
let double1 = (n) => n * 2;
// A može i ovako:
let double2 = n => n * 2;

alert( double1(3) ); // 6
alert( double2(3) ); // 6

• Izuzetak: Ako je funkcija bez parametara, mora se navesti zagrada:


let sayHi = () => alert("Hello!");
sayHi();
Streličasta sintaksa i funkcijski izrazi*
• Streličaste funkcije mogu se koristiti na isti način
kao i funkcijski izrazi, na primer, za dinamičko
kreiranje funkcije:
let age = prompt("Koliko Vam je godina?",
18);

let welcome = (age < 18) ?


() => alert('Ćao!') :
() => alert("Dobar dan!");

welcome(); // ok radi
Višelinijske streličaste funkcije
• Za složenije stvari (n.pr., višestruke izraze ili naredbe),
streličasta sintaksa omogućuje da se oni zatvore u vitičastu
zagradu.
• U tom slučaju obavezna je eksplicitna return naredba:
let sum = (a, b) => { // vitičasta zagrada otvara
// višelinijsku funkciju
let result = a + b;
return result; // ako se koristi vitičasta zagrada,
// obavezan je eksplicitan "return"
};

alert( sum(1, 2) ); // 3
Streličaste funkcije NEMAJU this*
• Streličasta sintaksa je specifična po pitanju this pokazivača – prosto, streličaste
funkcije NEMAJU funkcijsko this.
• Ako se koristi this, ono se uzima spolja (this.title u primeru je isti kao i tihs
spoljašnje metode showList, dakle - : group.title):
"use strict";
let group = {
title: "Naša grupa",
students: ["Jova", "Pera", "Vera"],

showList() {
this.students.forEach(
student => alert(this.title + ': ' + student)
);
}
};

group.showList();// Ispis: Naša grupa Jova, .....


Streličaste funkcije NEMAJU this*
"use strict";
let group = {
title: "Naša grupa",
students: ["Jova", "Pera", "Vera"],

showList() {
this.students.forEach(function(student) {
alert(this.title + ': ' + student) // Greška: ne može se
učitati // svojstvo 'title' za undefined

});
}
};

group.showList(); // ne radi
Streličaste funkcije NEMAJU
this3
• Nepostojanje this prirodno uzrokuje još jedno
ograničenje: streličaste funkcije ne mogu se koristiti
kao konstruktori, odnosno ne mogu se pozvati sa
new.
• Postoji i suptilna razlika između streličaste funkcije
=> i regularne funkcije pozvane sa .bind(this):
• .bind(this) kreira “vezanu verziju” funkcije ().
• Streličasta sintaksa => ne kreira nikakvo vezivanje;
funkcija, prosto, nema this.
• Pretraga za this je ista kao i za regularnu varijablu, traži
se u spoljašnjem leksičkom dosegu.
Streličaste funkcije NEMAJU
this4
const module = {
x: 42,
getX: function() {
return this.x;
}
}
 
const unboundGetX = module.getX;
alert('poziv unbound: ' + unboundGetX()); // Poziv funkcije u
globalnom
// dosegu. Izlaz: undefined
 
const boundGetX = unboundGetX.bind(module);
alert('poziv bound: ' + boundGetX()); // Očekivani izlaz: 42
Streličaste funkcije nemaju varijablu
arguments*
• Varijabla arguments je zgodna za dekoratorski obrazac kada treba
forvardovati poziv sa tekućim this i arguments.
• Na primer, defer(f, ms) prima funkciju f i vraća vraper oko
nje koji će odložiti njeno izvršavanje za ms milisekundi:
function defer(f, ms) {
return function() {
setTimeout(() => f.apply(this, arguments), ms)
};
}
function kaziZdravo(who) {
alert('Zdravo, ' + who);
}
let kaziZdravoOdlozi = defer(kaziZdravo, 500);
kaziZdravoOdlozi("Jovo"); // Nakon 5 sec ispis: //
Zdravo, Jovo
Funkcije: Prosleđivanje
podataka
Prosleđivanje podataka:
sadržaj
• Prametri/argumenti
• Pretpostavljeni parametri
• Rest parametri
• Spread sintaksa
• Doseg varijabli
• Globalni objekat
• Funkcijski objekat, imenovani funkcijski izraz
• new function konstrukt
• Povratni poziv, raspoređivanje izvršavanja: setTimeout i
setInterval
• Dekoratori i forvardovanje, call/apply
• Vezivanje (binding) funkcije
• Kuriranje
• Parcijalna aplikacija
Prosleđivanje podataka funkciji:
parametri/argumenti
• Funkcija prima ulazne podatke putem
parametara/argumenata
• Parametri se navode u deklaraciji funkcije
• Argumenti se navode pri pozivanju funkcije
• JavaScript je vrlo fleksibilna po pitanju broja
(ponekad i tipa) parametara i argumenata
Fleksibilnost parametri/argumenti: situacije
i posledice
• Fleksibilnost u odnosu broja parametara i
argumenata rezultuje sledećim situacijama:
• Da se prosledi baš onoliko argumenata koliko ima
parametara – ovde je sve “po pravilima službe”
• Da se prosledi više argumenta nego što ima parametara
– ovde se nešto mora uraditi sa “viškom” argumenata
• Da se prosledi manje argumenata nego što ima
parametara – ovde se mora rešiti pitanje “manjka”
argumenata
• Da treba prosleđivati različit broj argumenata pri
različitim pozivima – ovde se mora smisliti nešto što će u
sebi da čuva argumente za svaki pojedinačni poziv.
Prosleđivanje podataka: višak argumenata
• Argumenti koji su “višak” se, prosto, ignorišu.
• Primaju se 2 argumenta a prosleđeno je 5
argumenata (vratiće vrednost 3, a argumenti 3, 4, i
5 će biti ignorisani):
function sum(a, b) {
return a + b;
}

alert( sum(1, 2, 3, 4, 5) ); // 3=1+2


Prosleđivanje podataka: manjak argumenata*
• Ako argument nije prosleđen, njegova pretpostavljena vrednost je
undefined.
• Mogu se zadati i druge pretpostavljene vrednosti parametara koje
se koriste u tom slučaju. Sintaksa je:
imeParametra = pretpostavljena_vrednost
• Primer
function showMessage(from, text = "Nema teksta") {
alert( from + ": " + text );
}

showMessage("Marija"); // Marija: nema teksta


showMessage("Petar", "ima teksta" ); // Petar:
// ima teksta
Prosleđivanje podataka: rest sintaksa1
• Korišćenjem konstrukta ...imeNiza funkciji se može
proslediti proizvoljan broj argumenata:
function sumAll(...args) { // args je ime niza
let sum = 0;

for (let arg of args) sum += arg;

return sum;
}

alert( sumAll(1) ); // Vraća 1


alert( sumAll(1, 2) ); // Vraća 3
alert( sumAll(1, 2, 3) ); // Vraća 6

• rest parametri se koriste za kreiranje funkcije koja


prima proizvoljan broj argumenata.
Prosleđivanje podataka: rest sintaksa2
• Dozvoljeno je i “mešanje” (u sledećem primeru prva dva argumenta su varijable, ostali
se prosleđuju putem niza):
function showName(firstName, lastName, ...titles) {
alert( firstName + ' ' + lastName ); // Julije Cezar
// Cezar

// Ostali idu u niz


// t.j. titles = ["Consul", "Imperator"]
alert( titles[0] ); // Consul
alert( titles[1] ); // Imperator
alert( titles.length ); // 2
}

showName("Julius", "Caesar", "Consul", "Imperator");


• Ograničenje: rest parametri moraju da budu na kraju liste parametara:
function f(arg1, ...rest, arg2) – neispravno
function f(arg1, arg2, ...rest) - ispravno
Prosleđivanje podataka: Varijabla
arguments
• Postoji i specijalni nizoliki objekat koji se zove arguments koji
sadrži sve argumente uređene po indeksu.
• U starijim verzijama jezika nije bilo rest sintakse i jedini način za
prosleđivanje proizvoljnog broja argumenata funkciji bio je
objekat arguments koga još uvek ima u starijem kodu (i to radi).
• Nedostatak ovoga objekta je u sledećem:
• on nije tipa array (iako liči na niz i iterabilan je) pa ne podržava array
metode kao što je n.pr., arguments.map(...).
• Ne dozvoljava “mešanje” sa varijablama, već uvek mora da sadrži sve
argumente u sebi.
• Streličaste funkcije NEMAJU objekat arguments.
• Dakle, ako nam je potrebno ovakvo upravljanje prosleđivanjem
argumenata, bolje je koristiti rest parametre.
Prosleđivanje podataka: spread sintaksa1
• Sintaksa rest parametara omogućuje da od pojedinačnih argumenata
napravimo niz.
• Potrebno nam je i obrnuto: da sadržaj niza pretvorimo u pojedinačne
argumente:
alert( Math.max(3, 5, 1) ); // dobar je, vratio 5

let arr = [3, 5, 1];


alert( Math.max(arr) ); // nije dobro, vratio NaN
• To se radi korišćenjem spread sintakse:
alert( Math.max(3, 5, 1) ); // Vratio 5

let arr = [3, 5, 1];


alert( Math.max(...arr) ); // I ovo je dobro, vratio 5
• spread sintaksa se koristi za prosleđivanje niza funkciji koja normalno
zahteva listu više argumenata.
Prosleđivanje podataka: spread sintaksa2
• Kada se ...arr navede u pozivu funkcije, ona “razvija”
iterabilni objekat arr u listu argumenata.
• Na taj način može se prosleđivati više iterabilnih objekata:
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(...arr1, ...arr2) ); // Vraća: 8

• Mogu se kombinovati spread i “normalne” vrednosti:


let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // Vraća: 25


let merged = [0, ...arr1, 2, ...arr2]; // Šta će da vrati?
Prosleđivanje podataka: opcije za
pretvaranje u niz
let str = "Hello";
// spread sintaksa
alert( [...str] ); // Ispis: H,e,l,l,o
// Array.from
alert( Array.from(str) ); // Ispis: H,e,l,l,o
• Rezultat je isti i za [...str] i za Array.from(str). Ali
postoji suptilna razlikaizmeđu Array.from(obj) i
[...obj]:
• Array.from radi i nad nizolikim i nad iterabilnim.
• spread sintaksa radi samo nad iterabilnim.
• Dakle, bolje je da za pretvaranje nečega u array, koristite
Array.from (jer je univerzalniji).
Prosleđivanje podataka: Globalni objekat1
• Globalni objekat obezbeđuje varijable i funkcije koje su
svuda/svima dostupne. Ugrađen je u jezik ili okruženje.
• U brauzeru se ovaj objekat zove window, za node.js ime
je global, druga okruženja (mogu da) koriste i drugačija
imena.
• Odnedavno je jeziku JS dodat globalThis kao
standardizovano ime za globalni objekat, ali to još nije
univerzalno podržano. U svakom slučaju, može se lako
napraviti odgovarajući polifil.
• Ukoliko se ne zna sigurno u kom će se okruženju
izvršavati skript, najbolje je koristiti ime globalThis.
Prosleđivanje podataka: Globalni objekat2
• Svim svojstvima globalnog objekta može se pristupiti direktno:
alert("Zdravo");
// je isto što i
window.alert("Zdravo");
globalThis.alert ("Zdravo");
• Varijable deklarisane sa var postaju globalno dostupne, ali to
nije slučaj sa let:
var gVar = 5;
alert(window.gVar); // 5 (postala je svojstvo
// globalnog objekta)
let gLet = 5;
alert(window.gLet); // undefined (nije postala
// svojstvo globalnog objekta)
Prosleđivanje podataka: Globalni objekat3
// Učini informaciju o tekućem korisniku globalnom,
// tako da svi skriptovi mogu da joj pristupe

window.currentUser = {
name: "John"
};

// na drugom mestu u kodu


alert(currentUser.name); // John

// Ako imamo lokalnu varijablu sa imenom "currentUser"


// uzmi je iz objekta window eksplicitno (bezbedno!)
alert(window.currentUser.name); // John
Prosleđivanje podataka: Globalni objekat4:
primer polifila
if (!window.Promise) {
alert(“Brauzer vam je baš star!");
}

// zato ćemo napraviti polifil


if (!window.Promise) {
window.Promise = ... // implementacija Promise
}
Prosleđivanje podataka: Funkcijski objekat , NFE
(named Fuction Expression)
• Funkcija u JavaScript-u je vrednost
• Svaka vrednost ima svoj tip
• Kojeg je tipa funkcija?
• U JavaScript-u, funkcija je Function objekat:
• kod (function(){}).constructor === Function   vratiće vrednost
true
• Funkcije su posebni objekti akcionog tipa – mogu se pozivati
• Ali imaju i druge karakteristike objekata
• Imaju svojstva koja im se mogu dodavata/uklanjati
• Mogu se prosleđivati po referenci, itd.
Prosleđivanje podataka: Svojstva
funkcijskog objekta
• Globalni Function object nema sopstvenih
svojstava i metoda . Kako je sam funkcija, on
nasleđuje neke (ugrađene) metode i svojstva putem
prototipskog lanca od Function.prototype.
• Ovde ćemo objasniti ona koja se najčešće koriste:
• Function.prototype.name
• Function.prototype.length
Prosleđivanje podataka: Svojstvo
Function.prototype.name*
• Sadrži ime funkcije
• Deklaracija sa naredbom function
function kaziZdravo() {
alert("Zdravo!");
}
alert(kaziZdravo.name); // Ispis: kaziZdravo
• Funkcijski izraz sa deklaracijom let
let kaziZdravo = function() {
alert("Zdravo!");
};
alert(kaziZdravo.name); // Ispis: kaziZdravo
• Dodela putem pretpostavljene vrednosti
function f(kaziZdravo = function() {}) {
alert(kaziZdravo.name); // Ispis: kaziZdravo (radi!)
}

f();
I objektne metode imaju ime*
let user = {
kaziZdravo() {
// ...
},
kaziCiao: function() {
// ...
}
}

alert(user.kaziZdravo.name); //Ispis:kaziZdravo
alert(user.kaziCiao.name); // Ispis:kaziCiao
Ali ima i situacija kada se ime ne može
odrediti
// funkcija kreirana unutar niza
let arr = [function() {}];

alert( arr[0].name ); // <prazan string>


/* endžin nema načina da postavi ispravno
ime, onda imena nema */
Prosleđivanje podataka: Svojstvo
Function.prototype.length
• Sadrži podatak o broju parametara funkcije
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

alert(f1.length); // Ispis: 1
alert(f2.length); // Ispis: 2
alert(many.length); // Ispis: 2 (rest
// parametri se ne
// broje
Svojstvo length: jedno često korišćenje
• Za introspekciju (u računarstvu, introspekcija tipa je sposobnost
ispitivanja tipa ili svojstava objekta u vreme izvršenja programa) u
funkcijama koje operišu nad drugim funkcijama.
• Primer:
Funkcija ask prima argument question kao pitanje i proizvoljan
broj hendlerskih funkcija koje će se pozivati (po potrebi). Kada
korisnik da svoj odgovor, funkcija poziva hendlere. Mogu se
proslediti dve vrste hendlera:
• Funkcija bez argumenata koja se poziva samo ako korisnik da potvrdan
odgovor.
• Funkcija sa argumentima koja se poziva u svakom slučaju i vraća odgovor.
Da bi se hendler pozvao na ispravan način, može se ispitati
svojstvo handler.length.
Svojstvo length: kod za primer
korišćenja*
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// za potvrdan odgovor se pozivaju oba hendlera
// za negativan odgovor se poziva samo drugi hendler
ask("Pitanje?",
() => alert('Potvrdili ste'),
result => alert(result));
Dodavanje svojstava function objektu
• Ako želimo da brojimo pozive funkcije, to može da se uradi
dodavanjem svojstva brojac:
function kaziZdravo() {
alert("Zdravo!");
// hajde da brojimo koliko se puta izvršavamo
kaziZdravo.brojac++;
}
kaziZdravo.brojac = 0; // početna vrednost

kaziZdravo(); // Ispis: Zdravo!


kaziZdravo(); // Ispis: Zdravo!

alert( `Pozvao ${kaziZdravo.brojac} puta` ); // Ispis: Pozvao 2 puta


Svojstvo NIJE varijabla
function kaziZdravo() {
alert("Zdravo!");
// hajde da brojimo koliko se puta izvršavamo
kaziZdravo.brojac++;
}
let brojac = 0; // početna vrednost

kaziZdravo(); // Ispis: Zdravo!


kaziZdravo(); // Ispis: Zdravo!

alert( `Pozvao ${kaziZdravo.brojac} puta` );


// Ispis: Pozvao NaN puta
Svojstva ponekad mogu da zamenu
zatvaranja
• Ovde je broj uskladišten direktno u funkciju brojac, ne u njen
spoljašnji leksički doseg.
function napraviBrojac() {
// umesto:
// let broj = 0
function brojac() {
return brojac.broj++;
};
brojac.broj = 0;
return brojac;
}

let brojac = napraviBrojac();


alert( brojac() ); // Ispis: 0
alert( brojac() ); // Ispis: 1
Da li je to dobro ili nije?
• Glavna je razlika što eksterni kod ima pristup u ovom slučaju, pa
je moguće i sledeće:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}

let counter = makeCounter();

counter.count = 10;
alert( counter() ); // 10
Prosleđivanje podataka: new Function
konstrukt
• Ovo je još jedan način za kreiranje funkcije
• Ne koristi se previše često, ali po nekad ne može
drugačije
• Glavna razlika od drugih načina koje smo videli je u
tome što se funkcija kreira doslovce iz stringa koji se
prosleđuje u vreme izvršavanja.
• Sve prethodno viđene deklaracije zahtevaju da funkcijski
kod bude zapisan u okviru skripta.
• new Function omogućuje da se svaki string “pretoči” u
funkciju.
new Function konstrukt: sintaksa*
• Sintaksa je sledeća:
let func = new Function ([arg1, arg2, ...argN], functionBody);

• Primer:
let sum = new Function('a', 'b', 'return a + b');
alert( sum(1, 2) ); // Ispis: 3

• Primer:
let str = ... Primljeno dinamički sa servera ...

let func = new Function(str);


func();
new Function: Zatvaranje
• Videli smo da funkcija obično pamti gde je “rođena”. To se memoriše u
specijalnom svojstvu [[Environment]].
• A sama vrednost je pokazivač na leksički doseg u kome je funkcija kreirana.
• Međutim, kada se funkcija kreira pomoću new Function,
njeno [[Environment]] svojstvo se ne postavlja na tekući leksički doseg,
već se postavlja na globalni doseg.
• Zbog toga, takva funkcija nema pristup spoljašnjim varijablama, već samo
globalnim varijablama. To može da pravi problem zbog korišćenja
minifikatora (programi koji “komprimuju” JS kod tako što vrše izbacivanje
suvišnih stvari u šta spada i preimenovanje lokalnih varijabli u “kratka”
imena).
• Međutim, ova karakteristika funkcije je korisna u praksi jer zahteva da se za
prosleđivanje nečega funkciji kreiranoj pomoću  new Function eksplicitno
koriste njeni argumenti a ne zatvaranja, što je arhitekturalno mnogo čistije.
Povratni poziv
Šta je povratni poziv

• U računarskom programiranju, povratni poziv ili


naknadni poziv (engl. callback, call-after) je bilo
koji izvršni kod koji se prosleđuje kao argument
drugom kodu od koga se očekuje da povratno
pozove (u stvari, izvrši) prosleđeni argument u
nekom trenutku.
Funkcije povratnog poziva (callback)
• Napisati funkciju ask(question, yes, no) sa tri
parametra:
• question – tekst pitanja
• yes – funkcija koja će da se izvrši ako je odgovor “Yes”
• no - funkcija koja će da se izvrši ako je odgovor “No”
• Funkcija treba da postavi pitanje i da, u zavisnosti od
odgovora korisnika, pozove funkciju  yes() ili no()
Funkcije povratnog poziva (callback)
• Definisaćemo funkcije showOk i showCancel
koje se prosleđuju kao argumenti funkciji ask da se
kasnije izvrše.
• Ideja je da se funkcija prosledi i da se očekuje da
bude “povratno pozvana” kasnije, ako bude potrebno
• U primeru, showOk će se izvršiti za “yes” odgovor,
a showCancel za odgovor “no”.
• Argumenti showOk i showCancel funkcije ask zovu
se funkcije povratnog poziva ili samo povratni
pozivi.
Funkcije povratnog poziva (callback):
rešenje sa function deklaracijom
function ask(question, yes, no) {
if (confirm(question)) yes()
else no(); }

function showOk() {
alert("Saglasili ste se." );
}
function showCancel() {
alert("Otkazali ste." );
}

/* Korišćenje: funkcije showOk, showCancel se prosleđuju kao argumenti


funkciji ask */

ask("Da li ste saglasni?", showOk, showCancel);


Funkcije povratnog poziva (callback):
rešenje sa funkcijskim izrazom*
function ask(question, yes, no) {
if (confirm(question)) yes()
else no(); }
ask("Da li ste saglasni?",
function() { alert("Saglasili ste se."); },
function() { alert("Otkazali ste."); } );
Funkcije povratnog poziva (callback): sa
callback funkcijom*
function greeting(name) {
alert('Hello ' + name);
}

function processUserInput(callback) {
var name = prompt('Molim, unesite ime');
callback(name);
}

processUserInput(greeting);
Funkcije: vremensko raspoređivanje
izvršavanja
• Moguće je funkciju izvršiti odloženo – ne odmah, već nakon
određenog vremena.
• To se zove raspoređivanje poziva (“scheduling a call”).
• Za to postoje dve metode:
• setTimeout omogućuje da se funkcija pokrene jednom nakon
zadatog vremenskog intervala.
• setInterval omogućuje da se funkcija izvršava ponovljeno počevši
od nekog vremenskog intervala i kontinuirano ponavljajući izvršavanje
sa učestalošću određenom tim vremenskim intervalom.
• Ove metode nisu deo JavaScript specifikacije, ali većina
implementacija ima interni raspoređivač i pruža te metode.
Posebno, metode su podržane u baruzerima i okruženju
Node.js.
Funkcije: setTimeout
• Sintaksa
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
• Parametri:
• func|code - Funkcija ili string koda za izvršavanje. Obično je to
funkcija. Iz istorijskih razloga može se proslediti i
string koda, ali se to ne preporučuje.
• delay - vreme do pokretanja, u milisekundama;
pretpostavljena vrednost 0 (ako je 0, ne znači da
će se baš odmah izvršiti).
• arg1, arg2, ... - Argumenti funkcije (nije podržano u IE9-)
setTimeout: primer*
function kaziZdravo(fraza, ko) {
alert( fraza + ', ' + ko );
}

let timerId1 = setTimeout(kaziZdravo, 1000,


"Zdaravo", "Pero"); // Zdravo, Pero
Funkcije: setInterval
• Sintaksa
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
• Parametri:
• func|code - Funkcija ili string koda za izvršavanje. Obično je to funkcija. Iz istorijskih razloga može se
proslediti i string koda, ali se to ne preporučuje.
• delay - vreme do pokretanja, u milisekundama, pretpostavljena vrednost 0.
• arg1, arg2, ... - Argumenti funkcije (nije podržano u IE9-)

• Primer:
// ponavljaj svakih 2 sekunde
let timerId = setInterval(() => alert('tick'), 2000);

// nakon 5 sekundi stop


setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);

• clearInterval(timerId) - zaustavlja dalje pozive


Prosleđivanje podataka: Dekoratori i
prosleđivanje
• JavaScript pruža izuzetnu fleksibilnost u radu sa
funkcijama.
• Funkcije se mogu prosleđivati okolo, koristiti kao objekti.
• Pored toga, postoje još dve važne mogućnosti:
• Prosleđivanje poziva je prosleđivanje svih argumenata
zajedno sa kontekstom drugoj funkciji.
• Dekorator (Decorator) je specijalna funkcija koja prima drugu
funkciju i menja njeno ponašanje (podsetimo se obrasca
dekorator).
• Više detalja o ovim mogućnostima možete naći na adresi
https://javascript.info/call-apply-decorators
Prosleđivanje podataka: povezivanje
(binding) – gubljenje this*
• Pri prosleđivanju metoda objekta kao povratnih poziva (n.pr. funkciji
setTimeout) javlja se problem gubljenja pokazivača this
("losing this“).
• Primer:
let user = {
firstName: "Jovo",
kaziZdravo() {
alert(`Zdravo, ${this.firstName}!`);
}
};

let f = user.kaziZdravo;
setTimeout(f, 1000); // Zdravo, undefined!
Povezivanje (binding): Kada se gubi this
• Kada se metod prosledi odvojeno od objekta – gubi se
this.
let user = {
firstName: "Jovo",
kaziZdravo() {
alert(`Zdravo, ${this.firstName}!`);
}
};
// Ovde je setTimeout dobila user.kaziZdravo odvojeno od objekta.
let f = user.kaziZdravo;
setTimeout(f, 1000); // izgubljen user kontekst
Povezivanje (binding): Zašto se gubi this
• Metoda setTimeout u brauzeru je malo specijalna:
ona postavlja this = window za poziv funkcije(za
Node.js, to postaje timer objekat).
• Dakle, za this.firstName pokušava se dobavljanje
window.firstName, što ne postoji pa this
(uobičajeno u ovakvim slučajevima) postaje
undefined.
• Zadatak je tipičan – želimo da prosledimo metodu
objekta negde drugde (ovde – raspoređivaču) gde će
ona biti pozvana. Kako obezbediti da se ona pozove u
pravom kontekstu?
Gubljenje this: kako rešiti?
• Rešenje 1: Wraper – omotačka funkcija koja prima
kontekst (objekat) i zatim normalno poziva metodu.
• Rešenje 2: bind metoda – funkcije imaju ugrađenu
metodu koja se zove bind i koja rešava ovaj
problem
Rešenje 1: Omotačka funkcija
let user = {
firstName: „Jovo",
kaziZdravo() {
alert(`Zdravo, ${this.firstName}!`);
}
};

setTimeout(function() {
user.sayHi(); // Zdravo, Jovo!
}, 1000);
Rešenje 1: potencijalni problem
• Šta ako se pre trigerovanja funkcije setTimeout (a
ima 1 sekunda kašnjenja!) promeni vrednost user?
let user = {
firstName: "Jovo",
kaziZdravo() {
alert(`Zdravo, ${this.firstName}!`);
}
};
setTimeout(() => user.kaziZdravo(), 1000);
// ...vrednost promenljive user se promeni u toku te sekunde
user = {
kaziZdravo() { alert("Drugi korisnik u setTimeout!"); }
};
// Ispis: Drugi korisnik u setTimeout!
Rešenje 2: Ugrađena metoda bind*

let user = { let user = {


firstName: "Jova" firstName: "Jova"
}; };

function func() { function func() {


alert(this.firstName); alert(this.firstName);
} }

func(); // undefined let funcUser = func.bind(user);


funcUser(); // Jova
Rešenje 2: Ugrađena metoda bind
• func.bind(user) je “bound varijanta” od func, gde je
this = user.
• Svi argumenti se prosleđuju originalnoj funkciji func “as is” :
let user = {
firstName: "Jovo"
};
function func(phrase) {
alert(phrase + ', ' + this.firstName);
}
// fiksiraj this na user
let funcUser = func.bind(user);

funcUser("Zdravo"); // Zdravo, Jovo


// (argument "Zdravo" se prosleđuje i this = user)
Rešenje 2: Ugrađena metoda bind – slučaj
objekat*
let user = {
firstName: "Jovo",
kaziZdravo() {
alert(`Zdravo, ${this.firstName}!`);
}
};
let kaziZdravo = user.kaziZdravo.bind(user); // (*)
// može da radi bez objekta
kaziZdravo(); // Zdravo, Jovo!
setTimeout(kaziZdravo, 1000); // Zdravo, Jovo!
/* čak i ako se vrednost user promeni u toku te sekunde
čekanja kaziZdravo koristi prethodno uvezanu vrednost */
user = {
kaziZdravo() { alert("Drugi user u setTimeout!"); }
};
Ugrađena metoda bind – slučaj objekat sa
puno metoda*
• Za objekat sa puno metoda koje želimo da
prosleđujemo okolo, možemo vezivanje uraditi u
petlji:
for (let key in user) {
if (typeof user[key] == 'function') {
user[key] = user[key].bind(user);
}
}
Parcijalna aplikacija i
kuriranje
• Pri pozivanju funkcija imamo još situacija koje su u vezi sa
pozivanjem funkcije i veoma su važne za funkcionalno
programiranje:
• Parcijalnu aplikaciju funkcije,
• Kurirane funkcije
• Ovim pitanjem ćemo se još detaljno baviti u toku kursa a sada samo
da vrlo sažeto kažemo o čemu se radi:
• Parcijalna aplikacija funkcije je situacija u kojoj se u pozivu funkcije zadaje
samo deo njenih argumenata, a deo argumenata se zadaje kasnije (pri
drugim pozivima).
• Kurirane funkcije su funkcije koje u jednom pozivu prihvataju samo jedan
argument.
• I parcijalna aplikacija i kuriranje su u vezi sa redukcijom arnosti
funkcije i tesno su povezane sa zatvaranjem.
Rekurzija
Sadržaj
• Šta je rekurzija
• Kontekst izvršavanja i stek
• Rekurzija: da/ne?
• Praktična primena rekurzije
• Rekurzivne strukture podataka
Šta je rekurzija
• Kada funkcija rešava neki zadatak, ona može da
poziva i više drugih funkcija. Poseban slučaj je kada
funkcija poziva samu sebe. To se zove  rekurzija.
• Rekurzija je obrazac programiranja koji je koristan u
sledećim situacijama:
• Kada se zadatak može prirodno razložiti na više
jednostavnijih zadataka iste vrste,
• Ako se zadatak može predstaviti kao jednostavna akcija
plus jednostavnija varijanta istog zadatka.
• Ako se zadatak svodi na rad sa određenim strukturama
podataka.
Funkcija stepenovanja: dva načina
razmišljanja
• Napisati funkciju pow (x,n) koja vrši stepenovanje broja
• x – osnova
• n – stepen
• Dakle, računa se xn:
Može ovako (iterativno) A može i ovako (rekurzivno)
pow (x,0) = x0 = 1 pow (x,0) = x0 = 1
pow (x,1) = x1 = 1∙x pow (x,1) = x1 = x∙pow (x,0)
pow (x,2) = x2 = x∙x pow (x,2) = x2 = x∙x1= x∙pow (x,1)
pow (x,3) = x3 = x∙x∙x pow (x,3) = x3 = x ∙ pow (x,2)
....... .......
pow (x,n) = xn = x∙x∙ ... ∙x pow (x,3) = xn = x ∙ xn-1= x ∙ pow (x,n-1)
Funkcija stepenovanja:
iterativno*
function pow(x, n) {
let result = 1;

// U petlji n puta pomnoži result sa x


for (let i = 0; i < n; i++) {
result *= x;
}

return result;
}

alert( pow(2, 3) ); // Rezultat: 8


Funkcija stepenovanja: rekurzivno*
function pow(x, n) {
if (n == 1) {
return x; // Baza rekurzije
} else {
// Rekurzivni korak: Funkcija poziva samu sebe
return x * pow(x, n - 1);
}
}
 
alert( pow(2, 3) ); // Rezultat: 8
Zašto da rekurzija?
• Rekurzija je, obično, kraća. Stepenovanje možemo
programirati i pomoću uslova:
function pow(x, n) {
return (n == 1) ? x : (x * pow(x, n - 1));
}
• Ima puno zadataka gde rekurzivni način razmišljanja
daje jednostavniji kod, lakši za održavanje.
Zašto ne rekurzija?
• Dubina rekurzije je maksimalan broj ugnježdenih poziva,
uključivo i prvi (u primeru to je tačno n).
• JavaScript endžini ograničavaju maksimalnu dubinu
rekurzije:
• Može se smatrati da je ona oko 10000; neki endžini
dozvoljavaju više, ali je 100000 verovatno iznad granica većine
endžina.
• Postoje automatske optimizacije (završna (terminalna)
rekurzija, tail calls optimizations), ali one još nisu svuda
podržane i rade samo za jednostavne slučajeve.
• To ograničava primenu rekurzije, ali se ona ipak dosta
široko koristi.
Kontekst izvršavanja i stek
• Da se podsetimo: Kontekst izvršavanja je interna struktura podataka
koja sadrži detalje o izvršavanju funkcije: gde je trenutno tok
kontrole, tekuće varijable, vrednost ključne reči this (ovde je ne
koristimo) i još neke interne detalje.
• Jedan poziv funkcije ima tačno jedan pridruženi kontekst
izvršavanja.
• Kada funkcija napravi ugnježdeni poziv, dešava se sledeće:
• Tekuća funkcija se pauzira.
• Kontekst izvršavanja tekuće funkcije pamti se u posebnoj strukturi
podataka koja se zove stek konteksta izvršavanja.
• Izvršava se ugnježdeni poziv.
• Nakon što se ugnježdeni poziv završi, stari kontekst izvršavanja se pronalazi
na steku i spoljašnja funkcija nastavlja da se izvršava od mesta na kome je
pauzirana.
Kontekst i stek: poziv pow(2,3)
1. function pow(x, n) {
2. if (n == 1) {
3. return x;
4. } else {
5. return x * pow(x, n - 1);
6. }
7. }
8. // ovde je poziv
9. alert( pow(2, 3) );
Izvršavanje rekurzije: važne
stvari
• Dubina rekurzije u primeru je bila n = 3.
• Maksimalan broj konteksta u steku je jednak dubini rekurzije n.
• Kontekst zauzima memoriju.
• Zbog toga sa porastom dubine rekurzije rastu zahtevi za memorijom.
• Memorija koja je na raspolaganju za stek je ograničena
• Rezultat je da može (često) da vam se desi da program “pukne” zbog
prevelikog steka.
• Iterativni pristup odlikuje se manjim memorijskim zahtevima, jer ne
zavisi od n pa je zgodno rekurziju zamenuti iteracijom.
• Međutim, ova zamena može da bude netrivijalna, posebno kada
funkcija koristi različite rekurzivne podpozive koji zavise od nekih
uslova i spaja njihove rezultate, ili kada je grananje komplikovano. A
optimizacija može biti nepotrebna i zahtevati nesrazmerne napore.
Praktična primena rekurzije: rekurzivni
obilazak
• Kada se javi zadatak u kome treba operisati sa komplikovanijom
hijerarhijskom strukturom podataka, rekurzivni obilazak strukture može da
bude dobro rešenje.
• Posmatraćemo sledeći primer: Pretpostavimo da imamo neku kompaniju gde
zaposleni pripadaju departmanima. Pri tome važi sledeće:
• Departman može da ima niz zaposlenih. Na primer, departman Prodaja ima 2
zaposlena: Jova i Vera.
• Ili departman može da se podeli na poddepartmane (ogranke), kao što je Razvoj koji
ima dva ogranka : spoljašnjiRazvoj i unutrašnjiRazvoj. Svaki od ogranaka
ima svoje zaposlene.
• Moguće je i da se, kada naraste, poddepartman dalje deli na poddepartmane (n.pr.
timove). Na primer, spoljašnjiRazvoj može da se podeli na timove za lokaciju A
(timA) i lokaciju B (timB).
• I to nije sve, samo nešto što treba da ilustruje šta sve može da se desi.
• Konkretan primer podataka za ovakvu kompaniju predstavljen je sledećim
objektom
Objekat Company
let company = { razvoj: { drugi: [{
prodaja: [{ prvi: [{ ime: ‘Jakov',
ime: 'Jova', ime: 'Petar', plata: 1300
plata: 1000 plata: 2000 }]
}, }, }
{ { };
ime: 'Ana', ime: 'Aleksa',
plata: 600 plata: 1800
} }
], ],
Rekurzivni obilazak: sračunavanje iznosa
plata za firmu Company
• Iterativno: nije dobra ideja zato što:
• Imamo komplikovanu strukturu sa puno ugnježdenih petlji (po kompaniji, po
departmanu, po ogranku, po timu).
• A šta ćemo ako i timovi mogu da se dekomponuju na niže organizacione
jedinice?
• Da pokušamo rekurzivno:
1. To može da bude “jednostavan” departman sa nizom u kome su zaposleni, pa
sračunavanje plate možemo da uradimo u jednostavnoj petlji.
2. Ili to može da bude objekat sa N poddepartmana gde možemo da napravimo N
rekurzivnih poziva da bismo u svakom dobili sumu za podepartman i da te
rezultate saberemo.
• Prvi slučaj je baza rekurzije, trivijalan slučaj ako imamo niz.
• Drugi slučaj, kada imamo objekat, je rekurzivni korak. Složeni zadatak se
razlaže u podazadatke za manje departmane. Oni dalje mogu da se
razlažu, ali će pre ili kasnije da završe sa slučajem (1).
Rekurzivno sračunavanje iznosa plata*
let firma = { // struktura podataka
prodaja: [{ime: 'Jova', plata: 1000}, {ime: 'Ana', plata: 600 }],
razvoj: {
prvi: [{ime: 'Petar', plata: 2000}, {ime: 'Aleksa', plata: 1800 }],
drugi: [{ime: 'Jakov', plata: 1300}]
}
};

// Funkcija koja radi posao


function saberiPlate(departman) {
if (Array.isArray(departman)) { // slučaj (1)
// saberi niz
return departman.reduce((prev, current) => prev + current.plata, 0);
} else { // slučaj (2)
let sum = 0;
// Rekurzivno pozovi za poddeprtmane
for (let poddep of Object.values(departman)) {
sum += saberiPlate(poddep); // saberi rezultate
}
return sum;
}
}

alert(saberiPlate(firma)); // Ispis: 6700


Napomena:
• U ovom kodu koriste se neke napredne mogućnosti
(o njima će biti detaljno reči kasnije):
• Metoda arr.reduce koja koristi redukciju za
određivanje sume niza.
• Petlja for(val of Object.values(obj)) koja
iterira nad vrednostima objekta: Object.values vraća
niz vrednosti.
Rekurzivne strukture podataka: povezana
lista
• Rekurzivna (rekurzivno definisana) struktura podataka je
struktura koja sebe replicira u delovima.
• Mi ćemo se pozabaviti jednom takvom strukturom koja se
naziva povezana lista.
• Element povezane liste je rekurzivno definisan kao objekat sa:
• vrednošću.
• Svojstvom next koje pokazuje na sledeći element povezane liste, ili
ima vrednost null ako je to poslednji element u listi (kraj liste).
• Prednosti liste i nedostaci liste
• Prednost: brzo umetanje/brisanje elemenata
• Nedostatak: elementima se ne može pristupiti po poziciji, mora
uvek da se koristi next.
Povezana lista: Dve JS implementacije*
let list = { let list = { value: 1 };
value: 1,
next: { list.next = { value:
value: 2, 2 };
next: {
list.next.next =
value: 3,
next: {
{ value: 3 };
value: 4, list.next.next.next =
next: null { value: 4 };
}
} list.next.next.next.next
} = null;
};
Povezana lista: A obe reprezentacije
predstavljaju isto
next next next next
1 2 3 4 null

• Odavde se još jasnije vidi da ima više objekata i da


svaki od njih ima svoju vrednost (1, 2, 3, 4) i pokazivač
next koji pokazuje na sledeći objekat u listi.
• Varijabla list je prvi objekat u lancu a sledeći
pokazivače može se doći do svakog elementa iz liste.
JavaScript operacije sa listama
• Manipulacija sa pokazivačima (next)
• Razlaganje liste u dve liste:
let secondList = list.next.next;
list.next.next = null;

list 1 2 null

secondList 3 3 null

• Spajanje dve liste:


list.next.next = secondList;
JavaScript operacije sa listama
// Umetanje na početak liste i uklanjanje sa početka //
liste
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };

// umetni novi element na početak


list = { value: "novi", next: list };
// Ukloni element sa početka liste
list.next = list.next.next;
Funkcije: Sistem tipova i Hindley-Milner
signatura tipa
Sistem tipova
• U matematici, logici i računarskoj nauci sistem
tipova je formalni sistem u kome je svakom terminu
dodeljen "tip" koji definiše njegovo značenje i
operacije koje se nad njim mogu izvršiti.
• U praktičnom računarstvu - programiranju - tipovi
su meta-jezik koji pomaže da se izbegnu greške u
programima.
• U praksi se koriste različiti sistemi tipiziranja od
kojih je vrlo zastupljen sistem koji se zove "Hindley-
Milner" i koji ćemo sada da objasnimo.
Signature
• Funkcije imaju signature koje se sastoje od:
• Neobaveznog imena funkcije.
• Liste tipova parametara, u malim zagradama. Parametri
mogu a ne moraju da budu imenovani.
• Tipa povratne vrednosti.
• U JavaScript-u nije obavezna specifikacija signatura
tipa.
• JavaScript endžin određuje tipove u fazi izvršavanja
programa. Ako se obezbedi dovoljno indikatora, signaturu
mogu da zaključe razvojni alati kao što su integrisana
razvojna okruženja i analizatori koda koristeći analizu toka
podataka.
Notacija signatura
• JavaScript nema sopstvenu notaciju signatura funkcije, ali
ima nekoliko "standarda" koji su u opticaju: JSDoc koji je
istorijski značajan ali se ne koristi mnogo, TypeScript i Flow
kao najveći konkurenti trenutno.
• Ima i drugih stvari poput Rtype koji preporučuje Erik Eliot za
dokumentovanje (https://github.com/ericelliott/rtype)
• Konačno, to je i sistem koji koristi jezik Haskel, kurirani
Hindley-Milner tipovi (
http://web.cs.wpi.edu/~cs4536/c12/milner-damas_principal
_types.pdf
)
• Prosto, nema standardizovane notacije za JavaScript pa se
koriste različite stvari.
Hindley-Milner sistem signatura tipa
• Osnove
• Rezonovanje o tipovima i njihovim implikacijama
Hindley-Milner (HM) sistem tipa: osnove
• Sistem je vrlo jednostavan, brzo se može objasniti i ne
zahteva previše prakse da bi se do prihvatljivog nivoa
savladao taj mali jezik.
• Evo primera:
// capitalize :: String -> String
const capitalize = s => toUpperCase(head(s)) +
toLowerCase(tail(s));
capitalize('smurf'); // 'Smurf'
Hindley-Milner (HM) sistem tipa: osnove
• U HM sistemu, funkcije se pišu kao a -> b gde su
a i b varijable za bilo koji tip:
// capitalize :: String -> String
• Naravno, ovde ne ulazimo u implementaciju same
funkcije, jedino što nas interesuje (a i samo govori
puno o implementaciji funkcije) jeste signatura koja
kaže sledeće: to je funkcija koja prima tip String i
vraća tip String
Hindley-Milner (HM) sistem tipa: primeri

// strLength :: String -> Number


const strLength = s => s.length;
 
// join :: String -> [String] -> String
const join = curry((what, xs) => xs.join(what));
 
// match :: Regex -> String -> [String]
const match = curry((reg, s) => s.match(reg));
 
// replace :: Regex -> String -> String -> String
const replace = curry((reg, sub, s) => s.replace(reg,
sub));
Hindley-Milner (HM) sistem tipa: primeri
// match :: Regex -> String -> [String]
const match = curry((reg, s) => s.match(reg));
• Može se napisati i ovako:
// match :: Regex -> (String -> [String])
const match = curry((reg, s) => s.match(reg));
• Šta nam kaže novi zapis?
• Evo i primera primene:
// match :: Regex -> (String -> [String])
// onHoliday :: String -> [String]
const onHoliday = match(/holiday/ig);
Rezonovanje o tipovima i njihovim
implikacijama
• Rezonovanje o tipovima i njihovim implikacijama
ilustrovaćemo na primerima
// id :: a -> a
const id = x => x;

// map :: (a -> b) -> [a] -> [b]


const map = curry((f, xs) => xs.map(f));
Rezonovanje o tipovima i njihovim
implikacijama
1.Funkcija id prima bilo koji tip a i vraća nešto što je takođe tipa a. Varijable se mogu
koristiti u tipovima baš kao i u kodu.
2.Imena varijabli kao što su a i b su konvencija, dakle može se staviti bilo koje ime. Ipak,
važi jedno značajno pravilo: Ako je to ista varijabla (isto ime), to znači da moraju da
budu istog tipa. To je važno pravilo: a -> b može da bude proizvoljan tip a na
proizvoljan tip b , ali a -> a znači da mora da bude isti tip. Na primer, id može da
bude String -> String ili Number -> Number , ali ne može da bude String -> Bool .
3.Slično, map koristi varijable tipa, ali u tom slučaju uvodimo b koja može a ne mora da
bude istog tipa kao a . To se može čitati na sledeći način: map prima funkciju bilo kog
tipa a koja preslikava na isti ili različit tip b , zatim prima niz a -ova i za rezultat daje
niz b -ova.
• Ovaj tip signature doslovce kaže šta funkcija radi gotovo reč po reč. Njoj se prosleđuju
funkcija sa a na b i niz a , a ona vraća niz b . Jedina osetljiva stvar za njeno
funkcionisanje je pozivanje funkcije nad svakim a .
• Sposobnost rezonovanja o tipovima i njihovim implikacijama je veština od velike
važnosti za funkcionalno programiranje.
Sažetak
• Funkcija je jedan od glavnih koncepata u programskom jeziku JavaScript.
• Funkcija u jeziku JavaScript, ako se koristi na način saglasan sa principima
funkcionalnog programiranja, je ekvivalentna sa funkcijom definisanom
u matematici: prima jedan ili više ulaznih podataka i vraća jedan izlaz.
• Podaci mogu da budu i druge funkcije, odnosno funkcija može da primi
drugu funkciju kao ulaz a funkcija može i da vrati drugu funkciju kao
izlaz.
• Mehanizmi za prosleđivanje podataka među funkcijama u jeziku
JavaScript su veoma raznovrsni, od argumenata, preko dosega, do
vezivanja.
• JavaScript podržava veoma važan koncept koji se zove rekurzija.
• Rekurzija je mehanizam koji omogućuje da funkcija pozove samu sebe.
Literatura za predavanje
1. Z. Konjović, Funkcije u programskom jeziku JavaScript -
napredno korišćenje, Kurs Funkcionalno programiranje
školska 2020/21 (slajdovi sa predavanja), Univerzitet
Singidunum, dostupno na Teams platformi
2. Z.Konjović, Skripta Uvod u funkcionalno programiranje,
autorski rukopis, Univerzitet Singidunum, dostupno na
Teams platformi
3. I. Kantor, Advanced working with functions, dostupno na:
https://javascript.info/advanced-functions
4. K. Simpson, Lagano-funkcionalni JavaScript (Poglavlje 2),
dostupno na stranici predmeta
5. https://developer.mozilla.org/en-US/docs/Web/JavaScript/R
eference/Functions

You might also like