JavaScript: neobične stvari

http://mbranko.github.io/webkurs

Ovo je deo web kursa

Literatura

  • JavaScript Programmers Reference
  • JavaScript: The Good Parts
  • JavaScript Guide od Mozilla Developers Network.
  • Code Academy sa interaktivnim JavaScript lekcijama.
  • Khan Academy ima puno informacija o crtanju i animaciji.

Sadržaj

  1. Podaci u memoriji
  2. Funkcije
  3. Closure
  4. Nizovi
  5. Prototipsko nasleđivanje
  6. Globalni objekat
  7. Sa var i bez var
  8. var that = this
  9. Currying
  10. Kviz

#1: podaci u memoriji

Podsećanje: promenljive

Promenljiva je imenovani kontejner za vrednost.

Ime se zove i identifikator.

Podsećanje: promenljive


var x;
var y = "Hello JS!";
var z;
          

Ove kutije su promenljive i svaka od njih ima vrednost.

U njima se može nalaziti bilo koja JavaScript vrednost.

Podsećanje: promenljive


var x;
var y = "Hello JS!";
var z;
z = false;
z = 101;   // <---
          

Možemo joj dodeliti drugu vrednost nakon njenog kreiranja.

Opseg vidljivosti promenljive

Blokovi koda omeđeni vitičastim zagradama ne uvode poseban opseg vidljivosti!


// what is i, $, p, and q afterwards?

var i = -1;

for (var i = 0; i < 10; i += 1) {
  var $ = -i;
}

if (true) {
  var p = 'FOO';
} else {
  var q = 'BAR';
}
          

Opseg vidljivosti promenljive

Kod sa prethodnog slajda se zapravo ponaša ovako:


var i, $, p, q;  // all undefined

var i = -1;

for (i = 0; i < 10; i += 1) {
  $ = -i;
}

if (true) {
  p = 'FOO';
} else {
  q = 'BAR';
}
          

Kad se program pokrene, sve deklaracije promenljivih se premeštaju
na vrh tekućeg opsega.

Ključne reči

Ključne reči se ne mogu koristiti kao imena promenljivih:


null true false break do instanceof typeof
case else new var catch finally return void
continue for switch while debugger function
this with default if throw delete in try
class enum extends super const export import

implements let private public yield
interface package protected static
          

Vrednost i tip

Vrednost je atomički podatak kojim operišemo.

Tip definiše skup mogućih vrednosti.

Postoji tačno 6 tipova u JavaScriptu.

  1. undefined
  2. null
  3. boolean
  4. number (IEEE 754 64-bit double)
  5. string
  6. object

Prvih 5 tipova su primitivni tipovi.

Objekat

Svaka vrednost ovog tipa je referenca na neki objekat u memoriji. Takvu referencu u govoru često zovemo samo "objekat".

Objekat

Objekat je skup osobina (property).

Osobina je imenovani kontejner za vrednost (sa dodatnim atributima).

Objekat čine parovi ključ-vrednost

Ime osobine zove se ključ.

Objekat se posmatra kao skup parova ključ-vrednost.




Slični pojmovi postoje i u drugim jezicima: map, dictionary, hash table, associative array...

Određivanje vrednosti

  • Literalna vrednost - konstanta.
  • Izraz koji uključuje promenljivu ili osobinu objekta
  • Složeniji izrazi koji uključuju pozive funkcija ili operatore

Promenljiva vs osobina objekta


// Value containers
var y = "Hello!";
var w = {
    x: "test",
    y: 1234
};

            

// To get the values
y;      // "Hello!"
w;      // (the object ref)
w.x;    // "test"
w['x']; // "test"
w.y;    // 1234
w["y"]; // 1234
            

Inicijalizacija objekta i objektni literal


var w = {
    x: "test",
    y: 1234,
    z: {},
    w: {},
    "": "hi"
};          
            

var w = new Object();
w.x = "test";
w.y = 1234;
w.z = new Object();
w.w = new Object();
w[""] = "hi";

            

Ova dva fragmenta proizvode isti rezultat.

Promena vrednosti osobine

Možemo menjati objekat nakon kreiranja.


var obj = {
    1  : "Hello",
    "3": "Good",
    x  : "JavaScript",
    foo: 101,
    bar: true,
    "" : null
};

obj["2"] = "World";     // *1 Add & Set
obj["1"];               // *2 Get       -> "Hello"
obj[2];                 // *3 Get       -> "World"
obj[3];                 // *4 Get       -> "Good"
obj.foo = 202;          // *5 Set
delete obj.bar;         // *6 Remove
delete obj[""];         // *7 Remove
          

Čitanje osobine iz literala

Interpreter će vitičastu zagradu tumačiti kao "blok koda". Možemo naglasiti da se radi o objektu pomoću običnih zagrada (operator grupisanja).

Čitanje osobine nakon literala se ne preporučuje.


var w = { x: "test" };
var y = { x: "test" }.x;

w.x;                  // "test"
y;                    // "test"

{ x: "test" }.x;      // SyntaxError: unexpected .

({ x: "test" }.x);    // "test"
({ x: "test" }).x;    // "test"
({ x: "test" })['x']; // "test"
({ x: "test" }['x']); // "test"
          

Vrednost objekta je zapravo referenca #1


var x = { a: 100 };
var y = { a: 100 };       
            

Slično pojmu pointera/adrese u jezicima kao što je C.

Vrednost objekta je zapravo referenca #2


var x = { a: 100 };
var y = { a: 100 };
var z = y;

x === y; // false
y === z; // true     
            

Dodela vrednosti je kopiranje reference (adrese)!

Vrednost objekta je zapravo referenca #3


var x = { a: 100 };
var y = { a: 100 };
var z = y;

x === y; // false
y === z; // true 

z.a = 200;    
            

Osobini se uvek pristupa preko reference.

Vrednost objekta je zapravo referenca #4


var x = { a: 100 };
var y = { a: 100 };
var z = y;

x === y; // false
y === z; // true 

z.a = 200;

x.a; // 100
y.a; // 200
z.a; // 200    
            

Pojmovna zbrka

Kada kažemo promenljiva, osobina, simbol ili identifikator možda mislimo na:

  • sam simbol, što je zapravo ime
  • imenovani kontejner za vrednost
  • vrednost smeštenu u kontejneru

Kada kažemo objekat možda mislimo na:

  • vrednost objektnog tipa, zapravo referencu
  • objekat na koji referenca pokazuje
  • objekat kao tip podataka
  • standardni ugrađeni ECMAScript objekat koji se zove Object

#2: funkcije

Funkcije su prvoklasni objekti

Neke objekte zovemo funkcije.

Funkcija je objekat koji možemo pozvati.



Funkcije se zovu metode u nekim slučajevima.

Funkcijski izraz

Koristimo ključnu reč function praćenu listom parametara i blokom koda da kreiramo funkciju.


var a = 7;
var sayhi = function (name) {
    var a = "Hello " + name;
    console.log(a);
    return a;
};  
          

Funkcija se kao objekat može dodeliti promenljivoj ili proslediti kao parametar drugoj funkciji.

Poziv funkcije

Par zagrada iza imena predstavlja poziv funkcije. Parametri unutar zagrada se prosleđuju funkciji.

Svaki put kada se funkcija pozove, ona dobija svoj opseg vidljivosti sa lokalnim promenljivima i parametrima.


var a = 7;
var sayhi = function (name) {
    var a = "Hello " + name;
    console.log(a);
    return a;
}; 

sayhi("J"); // "Hello J"
a; // 7 
          

Pogrešan broj parametara funkcije

U JavaScriptu možemo pozvati funkciju sa pogrešnim brojem parametara.

Prilikom poziva nedefinisani parametri dobijaju vrednost undefined.


var f = function (a, b) {
    console.log(typeof b);
    return a + b;
};

f(3);       // NaN ("undefined" printed)
f(3, 4);    // 7   ("number" printed)
f(3, 4, 5); // 7   ("number" printed)
          

Funkcija koja ne vraća vrednost

Funkcija ne mora da eksplicitno vrati rezultat.

U tom slučaju vraća se vrednost undefined.


var f = function () {
    return;
};

var g = function () {
};

var foo = f();
foo; // undefined
g(); // undefined
          

Funkcijski izraz može biti imenovan


var fact = function (n) {
    if (n === 0) return 1;
    if (n > 0) return n * fact(n - 1);              // *1
};
fact(3);     // 6, which is the factorial of 3

var fact2 = fact;
fact = null; // But if the value changes...
fact2(3);    // TypeError: “null” is not function

var f = function fib(n) {
    if (n === 0) return 0;
    if (n === 1) return 1;
    if (n > 1) return fib(n - 1) + fib(n - 2);      // *2
};
f(10);       // 55

var g = f;
f = null;    // If the value changes...
g(10);       // 55  (still working)
          

Ime fib je dostupno samo unutar te funkcije!

Deklaracija funkcije #1

Funkcije se mogu pozivati pre deklarisanja zato što...


// We can invoke a function
// before its "declaration"

var x = 100;

plusOne(x); // 101
plusOne(y); // NaN

var y = 999;

function plusOne(n) {
    return n + 1;
}
          

Deklaracija funkcije #2

... sve deklaracije idu na početak opsega


var x, y, plusOne;       // Variable declarations

plusOne = function (n) { // Function declarations
    return n + 1;        // are also moved to the
};                       // top of current scope

x = 100;

plusOne(x); // 101
plusOne(y); // NaN

y = 999;
          

Poziv funkcije odmah nakon funkcijskog izraza

Interpreter će misliti da deklarišemo funkciju kada naiđe na function. Operatorom grupisanja možemo naglasiti da to tretira kao izraz.


(function () {
    return 123;
}());                                    // 123


(function(){ return 'hi'; })();          // "hi"
!function(){ console.log('ya'); }();     // true
void function(){ console.log('ya'); }(); // undefined
          

void operator za bilo koji operand uvek vraća undefined.
Može poslužiti da se function tretira kao izraz umesto kao deklaracija.
Može se pozivati sa zagradama kao void(0) ili bez zagrada kao void 0.

Metoda

Metoda je funkcija koja je osobina nekog objekta.


// The cat object has three properties
// cat.age, cat.meow, and cat.sleep

var cat = {
    age: 3,
    meow: function () {}
};
cat.sleep = function () {};

// We would say that cat.meow and
// cat.sleep are "methods" of cat
          

Referenca na objekat unutar metode

Tokom izvršavanja metode, referenca this (uglavnom) pokazuje na taj objekat.


var cat = {
    age: 3,
    meow: function () {
        console.log(this.sound);
        return this.age;
    },
    sound: 'meow~~'
};

cat.meow(); // 3  ("meow~~" is printed)

var m = cat.meow;
m(); // TypeError or undefined
          

#3: closure

Closure

Closure je par koji čine funkcija i okruženje za razrešavanje slobodnih promenljivih.

Leksičko okruženje #1

Kada koristimo promenljive iz globalnog opsega...


var a = 1;
var closureFunc = function () {
    // The "environment" (the outer scope)
    // determines to which value does the
    // symbol `a` resolve here
    console.log(a);
    return a;
};
          

Leksičko okruženje #2

Kada koristimo promenljive iz globalnog opsega...


var a = 1;
var closureFunc = function () {
    // The "environment" (the outer scope)
    // determines to which value does the
    // symbol `a` resolve here
    console.log(a);
    return a;
};

closureFunc(); // 1

a = 100;
closureFunc(); // 100

a = 'hello';
closureFunc(); // 'hello'
          

Leksičko okruženje #3

Okruženje se određuje u vreme kreiranja a ne u vreme izvršavanja!


var a = 1;
var closureFunc = function () {
    // The "environment" (the outer scope)
    // determines to which value does the
    // symbol `a` resolve here
    console.log(a);
    return a;
};

var anotherFunc = function () {
    var a = 2;
    return closureFunc();
};

anotherFunc(); // 1
          

Uobičajen način da kreiramo closure #1

Funkcija koja kreira i vraća closure funkciju pri svakom pozivu


var getClosureFunc = function () {

    var a = 1;
    var closureFunc = function () {
        // The "environment" (the outer scope)
        // determines to which value does the
        // symbol `a` resolve here
        console.log(a);
        return a;
    };

    return closureFunc;
};

var a = 3;
var clsr = getClosureFunc();

clsr(); // 1
          

Uobičajen način da kreiramo closure #2

Funkcijski izraz ugradimo u return...


var getClosureFunc = function () {

    var a = 1;
    return function () {
        // The "environment" (the outer scope)
        // determines to which value does the
        // symbol `a` resolve here
        console.log(a);
        return a;
    };
};

var a = 3;
var clsr = getClosureFunc();

clsr(); // 1
          

Uobičajen način da kreiramo closure #3

Closure se kreira kad se vraćamo iz anonimne funkcije


var clsr = (function () {

    var a = 1;
    return function () {
        // The "environment" (the outer scope)
        // determines to which value does the
        // symbol `a` resolve here
        console.log(a);
        return a;
    };

}());

var a = 3;

clsr(); // 1
          

JavaScript koristi statički/leksički opseg

Kako će se razrešiti identifikator u funkciji određuje se u vreme kreiranja funkcije.

Osim za this.

Život opsega

  • Život lokalnih promenljivih ne mora se završiti kada se funkcija završi.
  • Ako funkcija koja se vraća kao rezultat koristi bar jednu lokalnu promenljivu, kreira se closure.
  • Samo funkcije koje se vrate kao rezultat mogu da pristupe tim promenljivima.

var x = 999;

var f = function () {
    var x = 222;            // *1
    return function () {
        x += 100;
        return x;           // *2
    };
};

var g = f();                // *3
g();        // 322          // *4
g();        // 422          // *5
          

Imenovani funkcijski izrazi

  • Imenovani funkcijski izraz kreira closure sa slobodnom promenljivom tog imena vezanom samu funkciju.

var f = function fac(n) {
    if (n === 0) return 1;
    if (n > 0) return n * fac(n - 1);
};

//----- The above code works like: -----

var f = (function () {

    var fac = function (n) {
        if (n === 0) return 1;
        if (n > 0) return n * fac(n - 1);
    };

    return fac;
}());
          

#4: nizovi

Nizovi

  • Neki objekti zovu se nizovi.
  • Možemo ih inicijalizovati i pomoću literala.


var w = [
    "test",
    1234,
    {},
    [],
    "hi"
];

w[4]; // "hi"  
            

var w = new Array(5);
w[0] = "test";
w[1] = 1234;
w[2] = new Object();
w[3] = new Array();
w[4] = "hi";


w[4]; // "hi"
            

Dodavanje elementa na kraj niza

  • pomoću metode push
  • upisivanjem elementa u odgovarajući indeks

var arr = [ "test", 1234, {}, [], "hi" ];

arr.push("sixth"); // 6
arr.length;        // 6
arr[5];            // "sixth"

arr[7] = 012;      // 10
arr.length;        // 8

arr[6];            // undefined
arr[7];            // 10

arr[8];            // undefined
arr.length;        // 8
          

Iteracija kroz niz #1

  • niz ima osobinu length

var arr = [ "test", 1234, {}, [], "hi" ];

for (var i = 0; i < arr.length; i += 1) {
    console.log(arr[i]);
}
          

Iteracija kroz niz #2

  • pomoću metode forEach

var arr = [ "test", 1234, {}, [], "hi" ];

arr.forEach(function (val /*, i, arr*/) {
    console.log(val);
});
          

Iteracija kroz niz #3

  • pomoću metoda map, some, every

var arr = [ "test", 1234, {}, [], "hi" ];

arr.map(function (val /*, i, arr*/) {
    return typeof val;
});
          
  • map prolazi kroz ceo niz i vraća niz vrednosti izračunatih datom funkcijom
  • some se zaustavlja kad naiđe na true
  • every se zaustavlja kad naiđe na false

Iteracija kroz niz #4

  • primer za forEach, map, some, every

var arr1 = [1,3,5,7,9];
var arr2 = [1,2,5,7,9];

var isOdd  = function (n) { return n % 2 === 1 };
var isEven = function (n) { return n % 2 === 0 };
arr1.every(isOdd); // true
arr2.every(isOdd); // false
arr1.some(isEven); // false
arr2.some(isEven); // true

var print = function (n) { console.log(n) };
var addTen = function (n) { return n + 10 };
arr1.forEach(print); // 1, 3, 5, 7, and 9 are printed
arr1.map(addTen);    // [11,13,15,17,19]
          

Iteracija kroz objekat #1

  • Object.keys vraća sve ključeve - sve nazive osobina

var obj = { a: "test", b: 1234, c: "hi" };

Object.keys(obj); // [ "a", "b", "c" ]
          

Iteracija kroz objekat #1

  • Poziv Object.keys sa map vraća sve vrednosti osobina

var obj = { a: "test", b: 1234, c: "hi" };

Object.keys(obj).map(function (key) {
    return obj[key];
});
// [ "test",
//    1234,
//    "hi" ]
          

#5: prototipsko nasleđivanje

Tipovi podataka u JavaScriptu

  • undefined neinteresantan
  • null neinteresantan
  • logički koristan
  • broj koristan
  • string koristan
  • objekat <-- vrlo interesantan!

Objekat omogućava pravljenje organizovanih i ugnježdenih struktura.

Tip objekat se može razvrstati

  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error
  • Boolean
  • String
  • Number

Ima više podvrsta.

Wrapper objekti, slično kao u Javi.

Primitivni tipovi vs objekti

  • wrapper objekti imaju svoje osobine i metode

( true  ).toString(); // "true"
( false ).toString(); // "false"

( 3.14 ).toString();  // "3.14"
( 3.14 ).toFixed();   // "3"

( "Hello" ).length;           // 5
( "Hello" ).toUpperCase();    // "HELLO"
( "HELLO" ).slice(1, -1);     // "ELL"
( "Hello world" ).split("o"); // [ "Hell", " w", "rld" ]
          

Konstruktor

  • svaka vrsta objekta ima konstruktor
  • poziva se pomoću new operatora ili pomoću literala

Konstruktor

  • Konstruktor je funkcija koja ima osobinu prototype koja se koristi za nasleđivanje i deljene osobine
  • vrednost osobine prototype je ili null ili referenca na objekat

Primer konstrukcije objekta: kreiramo niz #1

  • ugrađeni objekat Array se koristi za kreiranje novog niza
  • objekat Array.prototype već sadrži dosta osobina:
    length, map, toString, forEach, indexOf, filter, ...

Primer konstrukcije objekta: kreiramo niz #2

var x = new Array(2);
  • kada se izvrši new, objekat se kreira sa osobinama koje definiše konstruktor
  • npr. osobinu length je postavio Array

Primer konstrukcije objekta: kreiramo niz #3

var x = new Array(2);
  • zatim se prototip objekta postavi na Array.prototype
  • ova interna osobina zove se [[Prototype]]

Osobine prototype i [[Prototype]]

  • svaki JavaScript objekat ima internu osobinu [[Prototype]]

  • koja se (najčešće) poklapa sa osobinom prototype njegovog konstruktora

  • reći ćemo X-ov prototip misleći na objekat ili null na koju pokazuje [[Prototype]] osobina od X

Pojam nasleđivanja

  • objekat nasleđuje osobine od svog prototipa

Osobine objekta i lanac prototipova

Za čitanje osobine X objekta Y:

  1. ako Y ima osobinu X, vrati njenu vrednost
  2. ako je Y-ov prototip jednak null, vrati undefined
  3. vrati vrednost osobine X Y-ovog prototipa

Za upis osobine X objekta Y sa vrednošću Z:

  1. ako Y nema osobinu X, kreiraj novu osobinu
  2. upiši vrednost Z u novu osobinu X objekta Y

Sopstvena i nasleđena osobina

  • sopstvenu osobinu objekat direktno sadrži
  • nasleđenu osobinu objekat ne sadrži direktno, već je ona osobina (sopstvena ili nasleđena) objektovog prototipa

Primer nasleđenih osobina


var arr = [ 11, 22 ];

arr.length;                       // 2
arr.toString();                   // "11,22"
arr.map(function(n){return n+1}); // [ 12, 23 ]


arr.hasOwnProperty('length');     // true
arr.hasOwnProperty('toString');   // false
arr.hasOwnProperty('map');        // false
Array.prototype.hasOwnProperty('toString'); // true
Array.prototype.hasOwnProperty('map');      // true
          
  • za objekat arr:
  • length je sopstvena osobina
  • toString i map su nasleđene

Pisanje novog konstruktora


var Cat = function (name) {
    if (name) this.name = name;
};
Cat.prototype.name = 'Nyan Cat';
Cat.prototype.meow = function () {
    return 'Meow~  I am ' + this.name;
};

var pusheen = new Cat('Pusheen the cat');


var nyancat = new Cat;  // () can be omitted when no args

pusheen.meow(); // "Meow~  I am Pusheen the cat"
nyancat.meow(); // "Meow~  I am Nyan Cat"
          
  • identifikator this pokazuje na novi objekat
  • kada se funkcija pozove kao konstruktor
  • pomoću operatora new

Nešto lakši način za kreiranje objekata


var catPrototype = {
    name: 'Nyan Cat',
    meow: function () {
        return 'Meow~  I am ' + this.name;
    }
};

var pusheen = Object.create(catPrototype);
pusheen.name = 'Pusheen the cat';

var nyancat = Object.create(catPrototype);

pusheen.meow(); // "Meow~  I am Pusheen the cat"
nyancat.meow(); // "Meow~  I am Nyan Cat"
          
  • kreiranje objekta sa direktno navedenim prototipom

Razne vrste objekata

MDN JavaScript Reference sadrži detalje o korišćenju ugrađenih objekata: Object, Function, Array, String, Date, RegExp, ...

#6: globalni objekat

Globalni objekat

  • globalni objekat čuva sve ECMAScript ugrađene objekte
  • globalna promenljiva se čuva u globalnom objektu kao osobina
  • npr. konstruktori Object, Array, itd. su osobine globalnog objekta
  • funkcije kao što je setTimeout su takođe osobine globalnog objekta

Pristup globalnom objektu

  • this u globalnom opsegu kada interpreter nije u striktnom modu
  • window u web čitaču
  • global u Node.js

  • osobina globalnog objekta dostupna je direktno pod svojim imenom ako neka druga promenljiva nije zauzela to ime u globalnom opsegu

#7: sa var i bez var

Deklaracija promenljive sa i bez var


var myNewVar = 1;     // using var to declare a variable.
myOtherNewVar = 2;    // var is optional.
alert(myNewVar);      // will alert 1
alert(myOtherNewVar); // will alert 2
          

Ključna reč var nije obavezna prilikom deklaracije promenljive.
Kada se promenljiva deklariše bez var, interpreter podrazumeva da koristimo promenljivu u višem opsegu od trenutnog. Ako je tamo ne nađe, tražiće je uz lanac opsega sve do globalnog opsega. Ako je ne nađe ni tamo, dodaće novu promenljivu u globalni opseg.

Deklaracija promenljive sa var smešta promenljivu uvek u tekući opseg.

Deklaracija promenljive sa i bez var

Deklaracija u globalnom opsegu


function testScope() {
    myTest = true; // now myTest is global.
    alert(myTest);
}
testScope();  // will alert "true"
alert(myTest); // will alert "true" as well, because now myTest is global.
          

Ograničavanje opsega sa var


function testScope() {
    var myTest = true;
    function testNestedScope() {
        var myTest = false;
        alert(myTest);
    }
    testNestedScope();
    alert(myTest);
}
testScope(); // will alert false, and then true.
          

Deklaracija promenljive sa i bez var

Pristup promenljivoj pre deklarisanja


function testScope() {
    var myTest = true;
    function testNestedScope() {
        alert(myTest);
        var myTest = false;
    }
    testNestedScope();
    alert(myTest);
}
testScope(); // will alert "undefined", and then true.
          

Eksplicitna verzija prethodnog listinga


function testScope() {
    var myTest = true;
    function testNestedScope() {
        var myTest;
        alert(myTest);
        myTest = false;
    }
    testNestedScope();
    alert(myTest);
}
testScope(); // will alert "undefined", and then true.
          

#8: var that = this

var that = this


var colors = ['red', 'green', 'blue'];

document.getElementById('element').addEventListener('click', function() {
   
    // this je referenca na element koji je primio klik

    var that = this;

    colors.forEach(function() {
        // this je undefined
        // that je referenca na element koji je primio klik
    });
});
          

Vrlo česta zakrpa :(

#9: currying

Poreklo JavaScripta

λ funkcija

  • tzv. anonimna funkcija
  • deo matematike zvane λ-račun
  • koja se bavi pojednostavljivanjem izraza u λ-izraze
  • radi otkrivanja prave prirode računskih operacija

λ funkcija

  • na primer funkcija:
    cube(x) = x * x * x
  • može se iskazati bez imena:
    (x) → x * x * x
  • i može imati samo jedan argument

Currying

  • funkcije sa više argumenata
  • moraju prvo proći proces koji se zove currying:
  • transformacija funkcije sa više argumenata na takav način da se može pozvati kao lanac funkcija, svaka sa po jednim argumentom


  • Haskell Curry (1900-1982)

Currying

  • npr. funkcija
    (x, y) → x + y
  • se može transformisati u
    x → (y → x + y)


  • ili u klasičnoj notaciji
    f(x, y) = x + y
  • se transformiše u
    g(y) = y → f(x, y)

Currying u JavaScriptu


function f(x, y) {
    return x + y;
}
          

će postati:


function g(x) {
    return function(y) {
        return x + y;
    };
}
          

Provera:


f(1, 2) === g(1)(2)
          

Currying u JavaScriptu

Pošto su funkcije first-class objects, može i ovako:


function g(h) {
    return function(y) {
        return h(h(y));
    };
}
          

Provera:


81 === g(function(x) {
    return x * x;
})(3);
          

Parcijalna aplikacija

Ako damo samo jedan argument funkciji...


function g(h) {
    return function(y) {
        return h(h(y));
    };
}

var n = g(function(x) {
    return x * x;
});
          

Možemo je koristiti više puta:


337 === n(3) + n(4);
          

Ovo se zove parcijalna aplikacija i nije isto što i currying.

Funkcije sa više argumenata

Za funkciju sa više od 2 argumenta


function f(x, y, z) {
    return x + y + z;
}
          

Pravilan način za parcijalnu aplikaciju je kroz bind


var f1 = f.bind(null, 1);
          

Poziv parcijalno apliciranoj funkciji vraća rezultat, a ne sledeću funkciju u lancu!


6 === f1(2, 3);
          

Kviz

cheat-sheet.js

Kviz #1: poređenje brojeva

Vrednost ovog izraza je true:

(1 + 2 === 3)

Vrednost ovog izraza je false:

(0.1 + 0.2 === 0.3)

Objasni zašto.

Kviz #2: vrednosti, reference, promenljive

Nakon izvršavanja sledećeg koda:

a = 3; b = a; b = 4;

vrednost a je i dalje 3, bez obzira na b. Ali nakon izvršavanja:

a.x = 3; b = a; b.x = 4;

vrednost a.x postaje 4. Objasni zašto.

Kviz #3: parametri funkcija

Ugrađena funkcija parseInt u globalnom objektu pretvara string u broj. Na primer:

parseInt('31')

To radi i za različite osnove:

parseInt('A', 16); // 10
parseInt('11', 8); // 9

Za dati niz i wrapper funkciju:

var arr = ['3', '4', '5', '6'];
var myParseInt = function (n) {
  return parseInt(n);
};

Rezultat ovih poziva je neočekivan:

arr.map(myParseInt); // [3, 4, 5, 6]
arr.map(parseInt);   // [3, NaN, NaN, NaN]

Objasni zašto.

Kviz #4: životni vek promenljive

U JavaScriptu, kada se funkcija pozove, neće uvek raditi tako što će lokalne promenljive čuvati na steku. Zašto?

Napomena: Lokalne promenljive i parametri se skidaju sa steka prilikom povratka iz funkcije.

Kviz #5: leksički opseg i closure

Realne primene JavaScripta često koriste single-threaded JavaScript interpreter koji ima event loop i job queue. Funkcija setTimeout prima dva parametra:

setTimeout(callbackfunc, millisec)

Prilikom poziva funkcija dodaje novi event i vraća se. Kada istekne millisec, u job queue se dodaje funkcija callbackfunc. Kada dođe na red u job queue, funkcija će biti pozvana. Recimo da smo hteli da ispišemo brojeve 1, 2, 3, 4, 5 posle 1, 2, 3, 4, 5 sekundi:

for (var i = 1; i <= 5; i += 1) {
  setTimeout(function () {
    console.log(i);
  }, i * 1000);
}

Dobijen je rezultat:

6   (printed after 1 second)
6   (printed after 2 seconds)
6   (printed after 3 seconds)
6   (printed after 4 seconds)
6   (printed after 5 seconds)
  1. zašto se to desilo?
  2. kako bismo ispravili problem uz zadržavanje for petlje?
  3. napravi rešenje pomoću Array.prototype.forEach

Kviz #6: prototipovi, nasleđivanje i this

Jedan način da podelimo string u niz karaktera je da pozovemo Array.prototype.slice za string objekat:

String.prototype.sliceAsAnArray = Array.prototype.slice;
('hello').sliceAsAnArray(); // [ 'h', 'e', 'l', 'l', 'o' ]
('world').sliceAsAnArray(); // [ 'w', 'o', 'r', 'l', 'd' ]

Prilikom poziva sliceAsAnArray, this pokazuje na anonimni string objekat. Array.prototype.slice će to iseći na delove

this[0], this[1], this[2], ..., this[this.length - 1]

Zašto ovo radi kako treba? Napomena: proveri svrhu call i apply metoda za funkcije.

Kviz #7: OOP bez new operatora - factory function

Napiši funkciju koja liči na konstruktor koja kreira novi objekat za svaki poziv bez korišćenja new operatora.

Ne zaboravi prototipsko nasleđivanje tako da sve zajedničke osobine mogu biti deljene preko roditeljskog objekta.

Napomena: Object.create(proto) vraća novi objekat sa njegovom osobinom [[Property]] inicijalizovanom na parametar proto.

Kviz #8: OOP sa privatnim osobinama

Napiši factory funkciju koja proizvodi objekte sa osobinama koje su dostupne samo njegovim metodama. Na primer, ako kreiramo objekat na sledeći način:

var myobj = createMyObjectFactory({
    param1: 100,
    param2: "test",
    param3: true
});

U objektu myObj postojaće i privatna osobina counter koja čuva broj poziva metode myObj.getData() i metoda myObj.getCounter koja vraća vrednost counter. Dakle, izraz myObj.counter treba da vrati undefined ali myObj.getCounter() treba da vrati tekući broj poziva:

myobj.getData();
myobj.getData();
myobj.getData();
myobj.counter;      // undefined
myobj.getCounter(); // 3

Napomena: closure.

Kraj dela

← Početak dela

⇐ Početak kursa