Scope: dio kôda unutar kojeg varijablu ili funkciju možemo referencirati po imenu
- global scope
- function scope - varijable i funkcije se mogu referencirati samo unutar funkcije gdje su deklarirane
- block scope - unutar npr.
for
petlje iliif
uvjeta, varijable definirane prekolet
iconst
su block scoped
Scope hoisting: JavaScript automatski prebacuje deklaraciju (ne i inicijalizaciju) varijable ili funkcije na vrh scope-a koji ih sadrži (function scope ili global scope). Vrši se za:
- varijable definirane
var
ključnom riječi - funkcije definirane koristeći
function declaration
Sljedeći kôd će u strict modu javiti grešku:
'use strict';
console.log(foo); // ReferenceError: foo is not defined
Ali ako varijablu foo definiramo nakon korištenja:
'use strict';
console.log(foo); // undefined
var foo = 5;
Ovo je zato jer gornji kôd JavaScript implicitno razumije kao:
'use strict';
var foo; // deklaracija foo podignuta na vrh njenog scope-a
console.log(foo); // undefined
foo = 5;
Block scope:
if (true) {
const foo = 'const je zakon';
}
console.log(foo); // ReferenceError: foo is not defined
(Link na CodePen, greška se vidi u browser konzoli)
for (let i = 1; i <= 3; i++) {
console.log(i); // 1, 2, 3 redom
}
console.log(i); // ReferenceError: i is not defined
(Link na CodePen, greška se vidi u browser konzoli)
Razlikujemo nekoliko načina definiranja funkcija:
-
Function declaration
foo(); // Ispisuje 'can be called before declaration' function foo() { console.log('can be called before declaration'); }
-
Function expression: spremanje funkcije u varijablu
- definicija funkcije nije hoist-ana, samo naziv varijable
- ne može se koristiti prije deklaracije
bar(); // TypeError: bar is not a function var bar = function() { console.log("can't be called before declaration"); };
(Link na CodePen, greška se vidi u browser konzoli)
-
Function constructor - funkcije definirane na ovaj način imaju pristup samo varijablama iz globalnog scope-a, ne iz parent scope-a. Ne preporučuje se korištenje ovog načina iz security i performance razloga
const adder = new Function('a', 'b', 'return a + b'); console.log(adder(2, 3)); // 5
Novi način definiranja funkcija uveden u EcmaScript 6:
- kraća sintaksa
- nemaju svoj
this
- implicitni
return
- bez
arguments
isuper
Primjeri sintakse:
(param1, param2, param3) => { console.log(param1, param2, param3) };
// implicitno vraća rezultat a + b, ali samo ako tijelo funkcije nije unutar {} zagrada
(a, b) => a + b;
// poziv ove funkcije će vratiti undefined
(a, b) => { a + b };
// zagrade oko argumenta su opcionalne ako funkcija ima samo jedan parametar
singleParam => { /* tijelo funkcije */ };
// funckija bez ulaznih parametara
() => { console.log('Hello world') };
Za razliku od arrow funkcija, svaka funkcija definirana function
ključnom
riječju ima svoj this
objekt, koji ovisi o načinu na koji je pozvana. Arrow
funkcije uvijek koriste this
od parent scope-a.
Na primjer, za
addEventListener
vrijednost od this
unutar funkcije je referenca na element, ali ako je handler
arrow funkcija, naslijediti će this
objekt od parent scope-a:
const button = document.createElement('button');
button.textContent = 'Click me';
document.body.appendChild(button);
button.addEventListener('click', function() {
console.log('this je ', this); // button element na koji smo kliknuli
});
button.addEventListener('click', () => {
console.log('this je ', this); // Window objekt ili undefined
});
Closure je funkcija koja ima pristup varijablama iz njezinog parent scope-a, tj. mjesta gdje je deklarirana.
Sve funkcije u JavaScriptu su closure funkcije.
let name = 'Ana Anić';
function greet() {
console.log(`Hello ${name}`);
}
greet(); // Hello Ana Anić
name = 'Mate Matić';
greet(); // Hello Mate Matić
Ugniježdena funkcija vidi varijable iz njenog parent scope-a i parent scope-a funkcija koje ju sadrže:
let punctuationMark = '!';
function makeGreeter(name) {
function greet() {
console.log(`Hello ${name}${punctuationMark}`);
}
return greet;
}
const greetWorld = makeGreeter('world');
const greetMary = makeGreeter('Mary Lou');
greetWorld(); // Hello world!
punctuationMark = '?';
greetWorld(); // Hello world?
greetMary(); // Hello Mary Lou?
Vježba Što će sljedeći kôd ispisati u konzolu?
let foo = 1;
function print1() {
let foo = 2;
print2();
function print2() {
let foo = 3;
console.log('foo je', foo);
}
}
console.log('foo je', foo);
print1();
Closure funkcije su moćan alat u funkcionalnom programiranju, ali postoje (rijetki) slučajevi gdje closure funkcije rade na način koji se na prvu ruku čini ne-intuitivnim.
Sljedeći kôd u svakoj iteraciji for
petlje stvara closure funkciju koja
ispisuje vrijednost od i
i sprema je u closures
array. Nakon što se petlja
izvrti poziva se svaki od spremljenih closure-a:
var closures = [];
for (var i = 0; i < 3; i++) {
closures.push(() => console.log(i));
}
closures.forEach(closure => closure());
Očekivali bi da ovo u konzolu ispiše:
0
1
2
ali zapravo će ispisati 3
tri puta.
(Link na CodePen)
Razlog je što closure nije samo funkcija, već funkcija i scope u kojem se
nalazi. U ovom slučaju sve tri funkcije dijele isti scope (u ovom slučaju
globalni) unutar kojeg je definirana varijabla i
. Kako se petlja već izvrtila
u trenutku poziva, vrijednost od i
je 3 i to je ono što sve funkcije ispisuju
u konzolu.
Srećom, ovo možemo lako popraviti ako koristimo ES6 let
umjesto var
:
const closures = [];
for (let i = 0; i < 3; i++) {
closures.push(() => console.log(i));
}
closures.forEach(closure => closure());
Ovo radi kako bi očekivali jer je i
varijabla definirana sa let
block
scoped unutar for
petlje pa je svaki closure vezan na svoju i
varijablu.
Postoji veliki broj različitih načina stvaranja objekata u JavaScriptu, što može biti zbunjujuće za početnike i one koji dolaze iz više objektno orijentiranih programskih jezika poput Jave ili C#. Ovdje su navedena tri načina i razlozi zašto u Adriatic.hr-u preferiramo zadnji :)
function Person(name, surname) {
this.name = name;
this.surname = surname;
}
Person.prototype.getFullName = function() {
return `${this.name} ${this.surname}`;
};
Person.prototype.introduce = function() {
console.log(this.getFullName());
};
const a = new Person('Mate', 'Matić');
a.introduce(); // Mate Matić
Svaki property na ovako stvorenom objektu je javan.
- uvedene u EcmaScript 6
- syntactic sugar za prototype-based nasljeđivanje iz prošlog poglavlja
- za sada nema privatne metode i property-je
class Person {
constructor(name, surname) {
this.name = name;
this.surname = surname;
}
getFullName() {
return `${this.name} ${this.surname}`;
}
introduce() {
console.log(this.getFullName());
}
}
const mate = new Person('Mate', 'Matić');
mate.introduce(); // Mate Matić
const button = document.createElement('button');
button.textContent = 'Introduce Mate';
document.body.appendChild(button);
// TypeError: this.getFullName is not a function, jer addEventListener bind-a svoj this na funkciju
button.addEventListener('click', mate.introduce);
// Da bi ovo radilo, moramo na introduce funkciju bind-ati objekt
button.addEventListener('click', mate.introduce.bind(mate));
Koristimo moć closure funkcija. Primjer:
function createPerson(name, surname) {
// Privatna metoda
const getFullName = () => `${name} ${surname}`;
// Javne metode i properties idu ovdje
return {
introduce() {
console.log(getFullName());
},
};
}
const ante = createPerson('Ante', 'Antić');
ante.introduce(); // Ante Antić
const button = document.createElement('button');
button.textContent = 'Introduce Ante';
document.body.appendChild(button);
// Ovo radi
button.addEventListener('click', ante.introduce);
Prednosti:
- imamo privatne metode
- nema
this
komplikacija - ako koristimo transpiler kao što je Babel kako bi nam kôd radio u starijim preglednicima, klase rezultiraju sa puno više kôda od object factory-ja.
Mane:
- lošije performanse od klasa (duplo sporije po nekim mjerenjima)
- Factory Functions in JavaScript | Fun Fun Function
- Composition over Inheritance | Fun Fun Function