Uvod

Prototipsko nasledjivanje (eng. prototypal inheritance) je vrsta nasledjivanja gde objekat nasledjuje svojstva direktno od drugog objekta (ne od nekogo šablona tj. klase). Postoje dva osnovna mehanizma koji omogućavaju prototipsko nasledjivanje:

  1. Prototipsko delegiranje
    Ovaj proces se zasniva se na principu da se svojstva i metode nasledjuju kroz “lanac povezanih prototype objekata” (eng. “prototype chain”).
  2. Concatenative inheritance
    Ovaj proces nasleđivanja svojstava se zasniva na direktnom kopiranju svojstva izvornog objekata. Ovaj mehanizam je prihvaćen i sa standardom ES2015, gde je predstavljen sa “Object.assign()” metodom.

NAPOMENA:
Uokviru standarda ES2015 je predstavljena syntax-a za kreiranje objekata preko klasa, medjutim to je samo estetska promena jer se u pozadini sav posao oko nasledivanja izvršava prema prethodno pomenutom mehanizmu “prototipsko delegiranje”.

Prototipsko delegiranje

Definicija

Prototipsko delegiranje je najvažniji mehanizam nasledjivanja objekta u JavaScript-u. Zasniva se na principu da se svojstva i metode nasledjuju kroz “lanac povezanih prototype objekata” (eng. “prototype chain”). Svaka funkcija (najčešće se misli na konstruktorsku f-ju) odmah po kreiranju generiše kao svojstvo svoj “prototype objekat”. Ovaj specifičan objekat je u prvi mah prazan, ali je povezan preko “lanca nasledjivanja” (eng. “prototype chain”) sa roditeljskim objektom. Da bi neki objekat mogao da prosledi svom nasledniku metodu, potrebno je da definiše metodu u njegovom prototype objektu, jer je tako pravi dostupnom ostalim objektima kroz lanac nasledjivanja. Lanac nasledjivanja je ustvari skup povezanih prototype objekata koji se završava zaključno sa specijalnim objektom sa generičkim nazivom Object.prototype

OBJAŠNJENJE:
Kada JavaScript engine traži neku metodu prvo proverava da li smo je definisali u okviru sopstvenog prototype objekata. Ukoliko ga ne nadje tu, onda ga traži u prototype objektu roditelja. Ali ukoliko ga i tamo ne nadje tražiće dalje duž lanca medjusobno povezanih prototype objekata sve dok ne dodje do poslednjeg u lancu “Objec.prototype”. Ako ga ne nadje ni u njemu izbacuje error.

Zbog čega koristiti prototipsko delegiranje?

Razlog za korišćenje prototipsko delegiranja kao glavnog načina nasledjivanja je dobra optimizacija resursa, a to se najbolje objašnjava kroz poredjenje sa ne-optimizovnim mehanizmom. Prvo ćemo generisati objekte samo na osnovu konstruktorske f-je, koristeći rezervisanu reč new. Tako generisani objekti u sebi dobijaju kopiju svojstava i metoda koji se nalaze u konstruktorskoj funkciji u trenutku generisanja.

Novo generisani objekat (mojAuto) je dobio kopiju svih svojstava. Stoga treba obratiti pažnju na činjenicu da ukoliko nakon generisanja objekta promenimo neko svojstvo u konstruktorskoj funkciji ono neće imati uticaja na instancirani objekat, jer oni nisu povezani:

Loša strana ovoga pristupa izlazi na videlo kada se na osnovu ovakve konstruktorske funkcije instancira veliki broj novih objekata. Razlog za to je činjenica da se pri instanciranju novih objekata u svakom objektu instanciraju i metode, stoga ovakav pristup nepotrebno troši resurse za instanciranje iste metode u svakom objektu.
Za dobijanja optimizovanijeg koda se preporučuje mehanizam “konstruktorska funkcija + prototipsko nasledjivanje”. Kod ovog mehanizma se metode koje su potrebne svim novim objektima definišu u “prototip objektu“. Takve metode smeštene u prototip objekat se instanciraju samo jednom u okviru tog roditeljskog objekta, dok se ostalim objektima (generisanim na osnovu roditeljskog) delegiraju duž lanca nasledjivanja. Svojstva su specifična za svaki objekat stoga njih definišemo u konstruktorskoj funkciji, jer tako svaki novi objekat dobija svoju kopiju sa specifičnim vrednostima.

Nakon generisanja novog objekta (“mojAuto”), njemu je dostupna metoda prikaziPoruku(), koja je instancirana jedino u roditeljskom prototype objektu.

NAPOMENA:
Nasledjivanje putem prototype objekata je “živa stvar”! Ukoliko se naknadno promeni svojstvo u prototype objektu, uticaće na sve generisane objekte koji nasledjuju to svojstvo duž lanca nasledjivanja.

Šta je Object.prototype?

funkcija objekt

Može se smatrati da pre nego što nastane prvi objekat u kodu (tačnije: “pre nego što se napiše prvi red koda”) ugradjena u sam JavaScript core, postoji funkcija koja je “majka” svih objekata, a čiji naziv je veoma “intuitivan” Object().
Pošto je funkcija i sama objekat, ona ima svojstva, a prototype svojstvo je jedno od njih. Ovo svojstvo je tipa objekt. Taj objekat nema neko “pametno” ime, već generičko, nastalo na osnovu pripadnosti funkciji “Object.prototype”. Ovaj objekat je predak svih objekata i prema mehanizmu prototipskog delegiranja ovaj objekat delegira sva svoja svojstva novo kreiranim objektima. Stoga svim novim objektima su u startu dostupna ova svojstva i metode. To isto važi za objekte koji nastaju nasledjivanjem jer se Object.prototype uvek nalazi na kraju prototipskog lanca nasledjivanja. Svojstva ovog objekta su specifična jer njihovi deskriptori svojstva imaju negativne vrednosti (writable=no; enumerable=no; configurable=no), a pošto svojojstva nisu enumerable, to znači da nisu dostupna kroz “for…in” loop.

.constructor

Ovo svojstvo ukazuje na konstruktorsku funkciju od koje je nastao prvobitni objekat, a ukoliko objekat nema svoju “ličnu” konstruktorsku funkciju (npr. objekt literal {}, nastao korišćenjem ključne reči new…), onda javascript engine svojstvo .constructor traži duž lanca nasledjivanja sve dok je ne nadje.

.__proto__

Ovo svojstvo (tzv. “dunder proto”, skraćeno od “double underscore proto”) ukazuje na na roditeljski objekat od koga naš objekat nasledjuje svojstva i metode. Sa ovim svojstvom možemo čak i da predefinišemo objekat od koga želimo da nasledjujemo, mada generalno trebamo izbegavati korišćenje ovog svojstva, jer je veoma sporo, a pored toga nije po ES standardu iako je prihvaćeno u svim browserima (izuzev IE<11). Ukoliko baš želimo da koristimo ovako svojstvo, bolje je da koristimo svojstvo getPrototypeOf() koje je prihvaćeno ES2015 standardom, a ima iste karakteristike kao __proto__.

Ovo svojstvo može da se koristi i ulančano, pa tako može da se dodje do bilo kog prototype objekta duž celog lanca nasledjivanja:

Updating Native Objects Prototypes

Pošto su i nativni objekti (String, Array, Number…) takodje objekti, onda se i na njih može primeniti mehanizam delegiranog nasledjivanja, pa čak i dodavanje novih svojstava početnom prototype objektu:

Primer

Sada možemo da koristimo metodu trim() na svim stringovima:

Mana ovog pristupa je ta što novije verzije JavaScript-a mogu imati novu funkcionalnost pod baš tim nazivom “trim” pa bi naša metoda pregazila standardizovanu metodu. Ovaj problem bi mogao da se reši na sledeći način:

Prototipsko nasledjivanje kroz primer

Princip rada nasledjivanja objekata delegiranjem svojstava ćemo najbolje objasniti kroz primere.

Osnovni objekat:

Krenućemo od jednostavne konstruktorske funkcije Pocetna() i njenog prototype objekta, pa ćemo generisati nove objekte koristeći ključnu reč new():

Konstruktorksa funkcija i njen prototype objekat u JavaScript-u su nešto najbliže što možemo “prići” pojmu klase iz drugih programskih jezika. Novo generisani objekat je pri samom činu stvaranja objekta kopirao sva svojstava iz konstruktorske funkcije (Pocetni()), i povezao svoj prototip objekat sa roditeljskim. Treba naglasiti da naš novi objekat (“pocetni1”), kao i svaki objekat generisan sa ključnom reči “new”, nema svoju konstruktorsku funkciju (nema “Pocetni1()” f-ju), pa njegovo svojstvo .constructor ukazuje na konstruktorsku funkciju Pocetni().

prototype dijagram 1

Ekstendovani objekat:

U narednom delu je opisan postupak kreiranja novog objekta koji ekstenduje funkciju Pocetni():

a) Kreiranje nove konstruktorske funkcije i povezivanje sa svojstvima Pocetni()

Kreiranje modifikovanog “šablon objekta” počinje sa definisanjem nove konstruktorske funkcije:

Da bi nasledili svojstava iz druge konstruktorske funkcije, koristimo metodu call(). Metoda call() omogućava da redefinišemo značenje ključne reči “this”. Nama je potrebno da “this” napisan u konstruktorskoj funkciji “Pocetni()”, ukazuje na novo kreirane objekte nastale korišćenjem funkcije ModifikovaniPocetni(). Stoga za prvi parametar metode (zadužen za redefinisanje “this”) napišemo “this”, jer time definišemo da značenje reči “this” ukazuje na “this” (a to je po korišćenju ključne reči new ustvari novo generisani objekat). Kroz ostale parametre metode call() prosledjujemo dva parametra neophodna za svojstvoPrvo i svojstvoDrugo.

b) Povezivanje prototype objekata sa lancom nasledjivanja

U zavisnosti na koji način želimo da dodelimo metode našem “ModifikovaniPocetni.prototype” objektu, povezivanje prototype objekta se može izvršiti sa:

  1. new()

    Pošto je svaki novo generisani objekat “zakačen” za lanac nasledjivanja, dodeljivanjem novo generisanog objekta našem prototype objektu omogućavamo našem prototype objekatu pristup “lanacu nasledjivanja”.

    Medjutim ovaj način nije preporučen jer njegovim korišćenjem nepotrebno ubacujemo i svojstva iz konstruktorske funkcije (svojstvoPrvo, svojstvoDrugo) u lanac nasledjivanja.

  2. Object.create()

    Ovaj pristup je preporučen, jer pored kreiranja novog objekta, “Object.create()” metoda takodje direktno povezuje dva prototype objekta (prototype objekat novo kreiranog objekta i roditeljski prototip). Ovim povezivanjem za roditeljski prototip, naš novi objekat se “zakačio” za ceo lanac nasledjivanja, pa su mu dostupne sve metode iz njega.

    Medjutim pošto pri ovakvom povezivanju prototype objekata nije korišćena naša konstruktorska funkcija “ModifikovaniPocetni()”, JavaScript engine je ne prepoznaje, pa će potražiti “dublje” duž lanca nasledjivanja, gde je i nalazi kao funkciju “Pocetna()”. Tako da metoda .constructor našeg novog objekta trenutno ukazuje na pogrešan objekat “Pocetni()”. Ovo nije dobro, pa je stoga potrebno da to i promenimo:

c) Dodavanje novih funkcionalnosti

Nakon ekstendovanja našeg novog objekta svojstvima i metodama “roditeljskog” objekta, možemo naš novi objekat “obogatiti” sa novim funkcionalnostima:

Čak možemo da menjamo ekstendovanu metodu, tako što definišemo u našem objektu metodu sa istim imenom koja će da “zakloni” (eng. shadowing) nasledjenu metodu.

Pa tako svi kreirani objekti na osnovu ovog modifikovanog imaju sledeće funkcionalnosti:

prototype dijagram 2

Objedinjeni kod iz primera:

Pogledajte slične članke...

Podelite:

Ostavite komentar