Funkcije su objekti
Postoji osam osnovnih tipova podataka u JavaScript-u. Sedam od njih su primitivni tipovi podataka (poput brojeva, teksta itd.) i osmi je neprimitivni tip — objekat, a funkcije su prvoklasni objekti koji se mogu pozvati sa zagradama ()
kako bi izvrsili naredbu / akciju. Drugim rijecima, moguce je raditi sa funkcijama kao sa objektima.
function sayHi() {
console.log('Greetings');
}
sayHi(); // Greetings
console.log(typeof sayHi); // function
console.log(sayHi instanceof Object); // true
Svojstva objekta
Funkcija moze biti korisna sama po sebi i nositi bezbroj dodatnih funkcionalnosti u svojstvima.
Jedna od osobina koju funkcije dijele sa objektima su svojstva (key: value
parovi i metode) koje im se mogu kreirati, modifikovati i brisati. Za one koji nisu upoznati sa ovim tipom podatka, ispod je definisan objekat sa jednim key: value
parom, da bi izvan objekta pristupili tom svojstvu i izmjenili njegovu vrijednost.
let sum = {
current: 0;
};
sum.current = 3 + 5; // modifikuje vrijednost
A ovako izgleda funkcija sum()
kojoj smo dodijelili isto svojstvo koristeci tacku sum.current
.
function sum(a, b) {
sum.current = a + b; // sum.current nije varijabla
}
sum.current = 0; // definisano svojstvo objekta funkcije
sum(3, 5);
console.dir(sum); // prikazuje svojstva objekta
Rezultat koda u konzoli:
Kako je ona poseban tip objekta, dodijeljena su joj neka korisna svojstva koji drugi tipovi objekata nemaju. U konzoli su prikazani sa tamnijim slovima (arguments
, caller
, length
, name
itd.). Current
svojstvo nije doslo sa funkcijom, definisano je u kodu.
Duzina parametara funkcije
length
svojstvo se odnosi na definisan broj parametara koje funkcija prihvata:
function add(a, b) {
return a + b;
}
console.log(add.length); // 2
Kontekstualno ime
Name
svojstvo, kako ocekujemo, daje pristup imenu funkcije.
function add(a, b) {
return a + b;
}
console.log(add.name); // add
Cak i kada se radi o varijabli kojoj je po kreiranju dodjeljena funkcija bez imena, svojstvo name
ce zakljuciti ime iz konteksta.
let add = function(a, b) {
return a + b;
};
console.log(add.name); // add
Koje ime ce imati izraz funkcije (function expression) ako varijabli dodijelimo funkciju sa imenom?
let add = function calculate(a, b) {
return a + b;
};
console.log(add.name); // calculate
Ili ako funkcija nije dodijeljena varijabli i nema ime?
let func = [function() {}];
console.log(func[0].name); // *crickets chirping*
console.dir(func[0]);
// ƒ anonymous()
// arguments: null
// caller: null
// length: 0
// name: ""
// prototype: {constructor: ƒ}
Prioritet uvijek ima ime deklarisano nakon kljucne rijeci function
, a ako ga uopste nema - onda se radi o anonimnoj funkciji sa name: ""
(vrijednost je prazan string).
Imenovani funkcijski izrazi
Deklaracija funkcije (function declaration) uvijek pocinje sa function imeFunkcije() {...}
, dok izraz funkcije (function expression) omogucava kreiranje nove funkcije unutar bilo kog izraza.
let func = function() {
// ...
};
Ovo je jedan primjer izraza funkcije jer se kreiranje funkcije desava u sklopu izraza — kroz dodjeljivanje anonimne funkcije varijabli. Izrazi zavrsavaju sa ;
da bi kompajler razlikovao i odvojio izraze. Funkcija nema ime i name
svojstvo ove funkcije je izvuceno iz konteksta (varijable func
).
Da bi kreirali imenovani funkcijski izraz (Named Function Expression), varijabli func
treba dodijeliti imenovanu funkciju:
let func = function read() {
// ..
}
console.log(func.name); // read
Kljucna stvar kod ovakve funkcije je da ona rezervise read
identifikator za unutrasnji opseg i predstavlja pouzdan nacin za pozivanje sebe. Pouzdan je jer se taj identifikator ne moze izmjeniti nakon kreiranja funkcije, cak i ako se promijeni ime varijable. Ali read
identifikator je pogodan za interno pozivanje sebe jer nije dostupan izvan funkcije, dok je func
ime varijable dostupno unutar i izvan funkcije i njeno ime se moze izmjeniti.
let func = function read(quote) {
if (quote) {
console.log(quote);
} else {
read("Lorem ipsum dolor sit amet.");
// poziva sebe sa argumentom
}
};
func(); // Lorem ipsum...
read();
// Uncaught ReferenceError: read is not defined
Metode funkcije
Kao i objekti, funkcije mogu imati metode, a neke su predefinisane (console.dir(add)
):
ƒ add(a, b) arguments: null caller: null length: 2 name: "add" [[Prototype]]: ƒ () bind: ƒ bind() toString: ƒ toString() Symbol(Symbol.hasInstance): ƒ [Symbol.hasInstance]() get arguments: ƒ () ...
func.toString()
u konzoli vraca tijelo funkcije kao string.
Mnoge JavaScript biblioteke koriste ovu osobinu funkcija. jQuery biblioteka ima glavnu funkciju $
i ostale pomocne funkcije koje se vezu za nju. Na ovaj nacin se izbjegava preopterecenje globalnog prostora i konflikti imenovanja varijabli i funkcija.
Prosljedjivanje po referenci
One se prosljedjuju po referenci (pass by reference) kao i drugi objekti (array
i object
), dok se primitivni tipovi podataka (svi ostali) prosljedjuju po vrijednosti. Ovdje je detaljnije objasnjeno sta to znaci i kako se razlikuju.
U sustini, kada varijabli dodijelimo funkciju, ona ne dobija kopiju funkcije, vec referencu koja pokazuje na mjesto u memoriji gdje se ona nalazi. Tako dvije varijable imaju razlicita imena iako pristupaju istoj funkciji.
let book = function() {
// ...
}
var book2 = book;
book.author = "Ana";
console.log(book.author); // Ana
console.log(book2.author); // Ana
book2.author = "Vana";
console.log(book.author); // Vana
console.log(book2.author); // Vana
Funkcije se mogu prosljedjivati kao argumenti drugim funkcijama. I u ovom slucaju argumenti su referenca na funkciju, ne njena kopija.
function calculate(callback, x, y) {
let result = callback(x, y);
console.log(result);
}
function add(x, y) {
return x + y;
}
function multiply(x, y) {
return x * y;
}
calculate(add, 2, 3); // => 5
calculate(multiply, 2, 3); // => 6