A JavaScript 2.0 jelenleg még csak egy terv a JavaScript nyelv továbbfejlesztésére. Az aktuális JavaScript verziótól lényegesen különbözhet, a távolabbi cél azonban az itt lefektetett irányokba való haladás, a különböző szolgáltatásoknak a böngészők újabb verzióiba ad hoc módon való beillesztése helyett. A JavaScript 2.0 fejlesztését az ECMA TC39TG1 munkacsoport erősen koordinálta, felügyelte. A JavaScript szorosan követi az ECMAScript Edition 4 szabványt fejlesztés közben. A szándék az volt, hogy a JavaScript 2.0 and ECMAScript Edition 4 hasonló nyelvek legyenek, de a JavaScript 2.0 tartalmazzon ezen felül plussz dolgokat a visszajelzések és tapasztalatok alapján, mielőtt szabványosítanak. A cél alapvetően az, hogy létrejöjjön egy nyelv, amiben lehet modulokat, osztályokat írni. Egyszóval szerepeljenek benne az objektum-orientáltság kellékei. Lehetőleg a nyelv legyen egyszerű, de ugyanakkor biztonságos programokat lehessen benne írni. A nyelv implementációja legyen kicsi, tömör, rugalmas, de ugyanakkor az írt program helyesen müködjön s a "teljesítménye" is megfelelő legyen. Az OOP szempontjából a leglényegesebb újítások itt össze vannak foglalva. (Eredetileg szerepelt volna a JavaScript 2.0-ban pl. egy függvény paraméterezésénél lehessen névvel is hivatkozni a paraméterre és ne kelljen betartani a paramétersorrendet, de ez végül kikerült belőle, ennek oka az volt, hogy a JavaScript 2.0 "kis" nyelv maradjon).
A legérdekesebb különbséget a Javascript 1.5-höz képest a megújult típusfogalom, és az osztályok bevezetése jelenti.
Immár létezik típusellenőrzés, a típus fogalma pedig a megszokott: típusértékek egy halmazát határozza meg, valamint műveleteket
ezen a halmazon. Egy típus értékhalmazát szűkíthetjük, így új típusokat hozhatunk létre. Ebben segít az osztály fogalma. Osztályokat a
class kulcsszóval definiálhatunk, például:
A példa Waldemar Horwat-tól, a JavaScript 2.0 megalkotójától származik.
Konstruktorokat az opcionális constructor attribútummal definiálhatunk. Nem kell megadnunk az attribútumot, ha a létrehozó függvény neve megegyezik az osztályéval. Ez lesz az ún. default konstruktor, mely meghívódik, ha egy példányt a new operátorral hozunk létre. Példa (szintén W.H.-tól):
Lehetőségünk van statikus metódusokkal konstruktorokat szimulálni: készítsünk egy statikus metódust, melynek neve megegyezik az osztályéval, és egy példányt ad vissza az osztályból. Ekkor ez a metódus konstruktorként fog viselkedni, azaz a new operátor ezt fogja meghívni. A statikus metódusnak természetesen nem kell új példányt létrehoznia, hanem visszaadhat egy már létező példányt.
Legyen a C osztály ősosztálya a B. C minden n konstruktorának tartalmaznia kell hívást a B vagy a C valamely konstruktorára, még mielőtt elérné a this mutatót, vagy befejeződne. A hívás lehet implicit is: ha valamelyik végrehajtási ágban nem adunk meg ilyen hívást, a fordító generálja azt az első utasítás elé, B.B formában. (Azaz az ősosztály default konstruktorát hívja meg.) Az explicit hívásnak a következő formái megengedettek:
super(argumentumok) | B default konstruktorát, B.B-t hívja. |
super.m(argumentumok) | B.m - et hívja, ahol m B valamely konstruktora. |
this(argumentumok) | C.C-t hívja. |
this.m( argumentumok ) | C.m-et hívja, m C-nek egy másik konstruktora. |
(Egy igazán zárójeles megjegyzés: hivatkozhatunk az adott osztály egy másik konstruktorára is, és az akár, egy harmadikra, de a lánc végén kell lennie egynek, amelyik az ősosztály valamelyik konstruktorára hivatkozik, mert a "körbehivatkozás"-nak nincs értelme. Az viszont igaz, hogy a"C.m kisebb C.n-nél, ha C.n meghívja C.m-et" reláció a várakozással ellentétben NEM részbenrendezés C konstruktorain, sőt ezek a bizonyos láncok, melyeket a részbenrendezés automatikusan adna, sem alakulnak ki feltétlenül.)
Ezeknek a hívásoknak teljes utasításoknak kell lenniük, nem lehetnek egy kifejezés belsejében.
Amint az a szintaxisból is sejthető, nincs lehetőség az öröklődési fában egynél több szintet ugrani felfelé, azaz csak a közvetlen ős
konstruktoraira hivatkozhatunk.
Ha egy osztályban deklarálunk egy változót, ahhoz automatikusan létrejön egy beállító és egy lekérdezo függvény. Ezek végzik a változó beállítását és lekérdezését. Ezek láthatósága alapesetben megegyezik a változóéval, de külön módosíthatjuk azt. Például egy private változó lekérdező függvényét publikussá tehetjük, így a változó az olvasás szempontjából publikusan viselkedik, azaz jobbértékként más osztályokban is használható:
A leszármazott osztály felüldefiniálhatja az ősosztály változóinak lekérdező és beállító függvényeit, amennyiben a változó nem final-ként van deklarálva. Mivel a final, a változók alapértelmezett attribútuma, a virtual kulcsszót kell használnunk az ősosztályban:
(szerzői) Megj.: ez egy nagyon érdekes, és időnként hasznos lehetőség, azonban számomra érthetetlen, hogy miért a final az alapértelmezés. Így már az ősosztály írásakor el kell döntenünk, hogy később felül akarjuk-e definiálni ezeket a függvényeket. Ha elfelejtjük kiírni a virtual-t, később kellemetlenségünk lehet belőle(vagy másoknak). Csak a hatékonyságot találtam, mint e megoldás mellett szóló érvet, de ez egy ilyen dinamikus script nyelvnél szerintem nem elegendő.
A JavaScript(1.5) prototípusokon alapuló objektum orientált nyelv, ellentétben a sokkal általánosabb osztály alapúval. Arról lehetne vitát folytatni, hogy egyáltalán objektum orientáltnak tekintjük-e, vagy csak objektumokon alapuló nyelvnek, de figyelembe véve, hogy itt egy script nyelvről van szó, akkor a klasszikus objektum fogalom már igen nehezen jöhet szóba.
Az osztály alapú nyelveknél, mint például a Java vagy a C++, külön van választva az osztály és a példány fogalma. Az osztály egy absztrakt dolog, aminek egy konkrét előfordulása a példány. A prototípus alapú nyelveknél, mint például a JavaScript, nincs meg ez a különbség, csak objektumok vannak. Egy objektumot lehet használni, mint egy új objektum template -je. Bármely objektumnak lehet definiálni a tulajdonságait (a továbbiakban property) a létrehozásakor, vagy futási időben.
Az osztály alapú nyelvekben egy elkülönített úgynevezett osztály definiciós részben adjuk meg az osztály metódusait. Itt adhatunk meg egy speciális metódust, amit a példányok létrehozására használunk. Ez az ún. konstruktor. Ebben az eljárásban adhatjuk meg a property -k (mezők) kezdőértékét, és hajhatjuk végre az egyéb létrehozáskor szükséges műveleteket. A new operátor ezen metódusok segítségével hoz létre példányokat. A JavaScript hasonló példát követ, csak nincs elkülönítve az osztálydefinició a konstruktortól. Helyette definiálni kell egy létrehozó függvényt ( function ), ami meghatározza a property -k kezdeti halmazát, és értékeit. Bármely JavaScript függvény lehet konstruktor. Emellett objektumokat létrehozhatunk implicit módon is, azaz nem kezdetben adjuk meg tulajdonságait, hanem már létrejöttük után.
Az osztály alapú nyelvekben az osztályhierarchiát az osztály definicióban kell megadni, azáltal, hogy megmondom, hogy egy alosztály melyik osztaly(ok)tól származzon. JavaScriptben az objektumok létrehozásánál adhatok meg, egy másik objektumot, mint prototípust, aminek következményeként örökli a property -jeit.
Az osztály alapú nyelvekben az osztály fordítási időben jön létre, míg a példányok létrejöhetnek fordítási és futási időben is. Nem lehet megváltoztatni a property -k (mezők) számát vagy típusát miután létre lettek hozva. A JavaScript-ben futási időben lehet hozzáadni vagy törölni propery -ket. Ha például egy olyan objektumnak adok egy új property -t, aminek vannak leszármazottai, azok is meg fogják kapni.
Az osztály alapú (Java) és a prototípus alapú (JavaScript) objektum rendszerek összehasonlítása :
Class-based (Java) | Prototype-based (JavaScript) |
---|---|
Az osztály és a példány két külön dolog. | Minden objektum példány. |
Egy osztályt meghatározni az osztálydefinicióval lehet. Példányokat létrehozni pedig a construktor-ral. | Definiálni és létrehozni objektumokat a létrehozó függvénnyel lehet. |
Egy egyszerű objektumot a new operátorral lehet létrehozni. | Hasonlóan. |
Egy objektum hierarchiát az osztálydefinicónál lehet létrehozni, amikor megadjuk egy osztálynak, hogy ő alosztálya egy már létező osztálynak. | Egy objektum hierarchiát azáltal hozhatok létre, hogy kijelölök egy objektumot, mint prototípust, társítva a létrehozó függvény mellé. |
A property-k öröklődnek az osztálylánc mentén. | A property-k öröklődnek a prototípus lánc mentén. |
Nincs lehetőség a property-k dinamikus változtatására. | A létrehozó funkció csak a property-k kezdő halmazát definiálja. Dinamikusan hozzáadhatunk, vagy elvehetünk property-ket egy különálló objektumból, vagy egy egész objektumhalmazból. |
Objektumokat több módon is létre tudunk hozni, a céljainktól függ, hogy melyiket választjuk:
Objektum literált akkor érdemes létrehozni, amikor egy objektumnak előre ismerjük a tulajdonságait. A tulajdonságokat (property) már létrehozáskor megadjuk, bár lehetőségünk van tulajdonságokat, és objektum-metódusokat utólag is felvenni, vagy törölni. Létrehozunk egy változót, majd az objektum tulajdonságait, és az objektum-metódusokat veszzővel elválasztva, egy nyitó {, és csukó } között felsoroljuk. Az objektum literál tulajdonságai alaptípusok, valamint tömbök, és újabb objektumok is lehetnek. Példa:
A függvény-konstruktoros objektumot tudjuk leginkább megfeleltetni a magasabb programozási nyelvekből ismert Osztály fogalmának. Létrehozunk egy függvényt, ami egyben egy objektum is, és ebből az objektumból a new kulcsszó segítségével további objektumokat tudunk példányosítani. A tulajdonságok értékét paraméterben, vagy setter függvényeken keresztül tudjuk megadni. Lehetőségünk van alapérték beállítására egy tulajdonság esetén, ekkor példányosításnál nem kell megadnunk ennek a tulajdonságnak az értékét, hanem null értéket adunk át, így a tulajdonság értéke az alapértelmezett lesz. Ha így hozunk létre egy objektumot, akkor a tulajdonságok, és metódusok láthatóságát háromféleképp tudjuk szabályozni. Privát láthatóság, publikus láthatóság, és protected láthatóság. Erről később lesz szó. Míg objektum literál esetén egy konkrét objektumot, példánkban egy mustang autót hoztunk létre, addig ezzel a módszerrel egy kocsi típust hozhatunk létre, majd ebből a kocsi típusú objektumból (~osztályból) példányosíthatnánk egy mustang autót. Példa:
A JavaScript beépített Object objektumának create() metódusa segítségével is tudunk új objektumot létrehozni, ennek előnye, hogy függvény konstruktoros létrehozás nélkül tudunk szülő objektumot meadni, amiből az új objektum származni fog.
Az objektum tulajdonságait elérhetjük objektumnév.tulajdonságnév módon. Mivel az objektumok kezelése hasonlít a tömbök kezeléséhez, a nyelv megengedi a tömbelemként történő hivatkozást is: objektum[tulajdonság]. Sőt, hivatkozhatunk a tulajdonságokra indexekkel is, ekkor az egyes tulajdonságok 0-tól szerepelnek a deklarálásnak megfelelő sorrendben. Továbbá a for .. in ciklus alkalmas objektumok tulajdonságainak bejárására. Az Objekt.keys() metódus a tulajdonságok neveit adja vissza, nem az értéküket. Az Object.getOwnPropertyNames() metódus csak a saját tulajdonságot neveit adja vissza, az örökölteket nem! Például:
Konstruktoros objektum-típus létrehozás esetén lehetőségünk van publikus, privát, és protected láthatósággal rendelkező adattagok felvételére. A this.adattag jelöli a publikus tulajdonságokat és metódusokat, a var kulcsszóval bevezetett adattagokat nevezzük privát, kívülről nem lekérdezhető tulajdonságoknak. A function függvénynév() { .. } szintaktikával felvett objektum metódusok a privát metódusok, és csak a protected metódusok érhetik el az objektum típus privát adattagjait.
A getter egy olyan metódus amit egy tulajdonság értékének lekéréséhez használunk. A setter is egy metódus, ezzel egy tulajdonság értékét tudjuk beállítani. Bármilyen olyan beépített, vagy felhasználó által létrehozott objektumnál tudunk definiálni gettert és settert, ahol az utólagos metódus definiálás engedélyezve van.
Objektumok tulajdonságait a delete operátorral törölhetjük, ami nem értékének törlését jelenti, hanem konkrétan az adott attribútum törlését. Ez csak implicit módon létrehozott objektumok esetén használható.
Komplex Példa:
A példánkban a következő hierarchiát hozzuk létre.
Az Employee -nek van két property -je a name (aminek a kezdeti értéke egy üres karaktersor) és a dept (aminek "general" ).
A Manager az Employee -n alapul, de neki van még egy report property -je (aminek a kezdeti értéke egy üres vektor).
A WorkerBee is szintén az Employee -n alapul, és neki egy projects property -je van (aminek a kezdeti értéke szintén egy üres vektor).
A SalesPerson a WorkerBee -n alapul, egy új quota propery -vel (aminek a kezdőértéke 100 ), és felülírja a dept property -t a "sales" értékkel.
Az Engineer is a WorkerBee -n alapul, és neki is van egy új propery -je a machine (aminek a kezdőértéke egy üres karakterlánc), és ő is felülírja a dept property -t, de az "engineering" értékkel.
Most nézzünk egy példát a számtalan lehetőség közül, hogy hogyan lehet ezt megvalósítani.
Ha pusztán az alap objektum-definiciókat összehasonlítjuk a Java-val igen sok hasonlóságot fogunk tapasztalni.
JavaScript | Java |
---|---|
function Employee () {
this.name = "";
this.dept = "general";
}
|
public class Employee {
public String name;
public String dept;
public Employee () {
this.name ="";
this.dept ="general";
}
}
|
Az igazi különbséget a Manager és a WorkerBee definiciója mutatja meg. JavaScript-ben a prototípus példány meghatározása annyi, hogy értéket adok a prototype propery -ének a létrehozó függvénynek. Ezt a függvény definiciója után bármikor megtehetem. Java-ban meg kell adni az ősosztályt az osztály definiciójában, és ezen kívül már nem is tudok rajta változtatni.
JavaScript | Java |
---|---|
function Manager () {
this.reports =[];
}
Manager.prototype = new Employee;
function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;
|
public class Manager extends Employee {
public Employee[] reports;
public Manager () {
this.reports = new Employee[0];
}
}
public class WorkerBee extends Employee {
public String[] projects;
public WorkerBee () {
this.projects = new String[0];
}
}
|
Az Engineer és a SalesPerson definiálása hasonló módon történik. A property -k felülírására ( override ) nem kell különösebb trükköt alkalmaznunk,de érdemes odafigyelni rá.
JavaScript | Java |
---|---|
function SalesPerson () { this.dept = "sales"; this.quota = 100; } SalesPerson.prototype = new WorkerBee; function Engineer () { this.dept = "engineering"; this.machine = ""; } Engineer.prototype = new WorkerBee; |
public class SalesPerson extends WorkerBee { public double quota; public SalesPerson () { this.dept = "sales"; this.quota = 100.0; } } public class Engineer extends WorkerBee { public String machine; public Engineer () { this.dept = "engineering"; this.machine = ""; } } |
Felhasználva ezeket a definiciókat, létrehozhatunk objektumokat, amik megkapják a megfelelő kezdőértékeket.
Futási időben hozzáadhatunk egy új property -t bármelyik objektumhoz. Egyszerűen csak hozzá kell rendelni egy értéket.
Most már a mark objektumnak van egy új property -je, de semelyik másik WorkerBee -nek nincs. Hogyha annak az objektumnak adok új property -t, amelyik prototípusként fel lett használva a létrehozó függvénynél, akkor az összes olyan objektum megkapja, aki töle örökli a property -jeit. Például, ha akarok adni egy speciality property -t minden Employee -nak:
A JavaScript 2.0 az egyszeres öröklődést támogatja. Az ősosztályt az extends kulcsszóval adhatjuk meg. Az OOP-ben megszokott valamennyi fogalmat megtaláljuk, ezeket most nem részletezzük. Egy metódus felüldefiniálásakor ki kell írnunk az override attribútumot. Erre azért van szükség, hogy a hibásan írt metódusnevekre a fordító figyelmeztethessen bennünket.
Először létrehozunk egy FooBase osztályt, aminek lesz egy váltózója, és ennek a változónak lesz getter és sette metódusa, továbbá egy toString metódusa. Ez lesz a szülő osztály.
Most pedig létrehozunk egy FooChild osztályt, ami megörökli a FooBase nevű osztályunk tulajdonságait.
Most már akár le is tesztelhetjük, hogy miként viselkedik a két osztály toString() metódusa:
Feljebb olvashattátok, hogy a JavaScript prototypus alapú nyelv, ez azt jelenti, hogy minden objektumnak van egy prototype adattagja ami egy másik objektumra mutat. Ezt a másik objektumot hívjuk a szóbanforgó objektum prototypusának. Az objektum prototípusának is van egy prototypusa, és így tovább. Ezt nevezzük prototype chain-nek, vagy magyarul prototípus láncnak. A lánc végén a null objektumra mutató objektum van.
Amikor létrehozunk egy új objektumot (gyerek) példányosítással (szülő), akkor a gyereknek létrejön egy [[Prototype]] "virtuális" attribútuma amely a szülő objektum prototípusára mutat. Ha a gyerek objektum egy függvényét meghívjuk, akkor a JavaScript először megnézi a saját tulajdonságokat és metódusokat, ha megtalálta a hívott metódust, akkor nem keres tovább, ha nem találta meg a saját tulajdonságok között, akkor a [[Prototype]]-ban keres tovább, vagyis a szülő objektumban. Ezért van az, hogy ha hozzáadunk egy új metódust egy szülő objektum prototípusához, akkor ez a metódus a szülőből már korábban páldányosított objektumok esetén is hívható lesz, ugyanis a gyerekek sikertelen metódus-keresést követően a szülő prototípusában keresik a metódust.
Mint említettük, a [[Prototype]] attribútum virtuális, ám van egy valóságos tulajdonság, amit __proto__ -nak nevezünk, és ami a szülő prototípusára mutat. Példa:
A JavaScript 2.0 Kidolgozásra Vár
A JavaScript 2.0-ban lehetőségünk van interfészek használatára.
Példa: