Skip to content

Latest commit

 

History

History
423 lines (310 loc) · 12.4 KB

basics.md

File metadata and controls

423 lines (310 loc) · 12.4 KB

Scope

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 ili if uvjeta, varijable definirane preko let i const 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

Primjeri

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;

(Link na CodePen)

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)

Funkcije u JavaScriptu

Razlikujemo nekoliko načina definiranja funkcija:

  • Function declaration

    foo(); // Ispisuje 'can be called before declaration'
    function foo() {
      console.log('can be called before declaration');
    }

    (Link na CodePen)

  • 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

    (Link na CodePen)

Arrow funkcije

Novi način definiranja funkcija uveden u EcmaScript 6:

  • kraća sintaksa
  • nemaju svoj this
  • implicitni return
  • bez arguments i super

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
});

(Link na CodePen)

Closures

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ć

(Link na CodePen)

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?

(Link na CodePen)

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();

(Link na CodePen)

Closure unutar for petlje

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());

(Link na CodePen)

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.

Stvaranje objekata u JavaScriptu

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 :)

1. Koristeći funkcije, new keyword i prototype

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ć

(Link na CodePen)

Svaki property na ovako stvorenom objektu je javan.

2. Klase

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));

(Link na CodePen)

3. Object factories

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);

(Link na CodePen)

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)

Korisni linkovi