Javascript O(O)P
A webfejlesztésnek, akár akarjuk, akár nem, bizony szerves részét képezi a frontend, így az életutunkbizony keresztezni fogja némi javascript, kivéve, ha dedikáltan backendesek vagyunk.
Viszont amit a javascript terén teszünk, az az esetek túlnyomó többségében nem nevezhető fejlesztésnek, inkább csak különböző library-ket rántunk be, esetleg jquery selectorokat hívogatunk meg és próbaljuk minimálisra csökkenteni azt az időt, amíg az IDE-ben JS kód van megnyitva.
Ha a kódunk tényleg csak a globális névtérbe dobált változók tömkelege, akkor sajnos a helyzet nem is fog változni, főleg akkor ha Php-ben megszoktuk a jól struktúrált/névterekre bontott kódot.
Pedig a javascript is tud ám ilyet! (a maga módján)
Tegyük fel, hogy nem szeretnénk a globális névteret teleszemetelni, így névtereket akarunk definiálni:
Mi sem egyszerűbb? Javascriptben az egész annyi, hogy létrehozzuk a hozzá tartozó objektumot:
1
var MyNamespace = {};
Bumm, kész is van a névterünk! Viszont ezzel túl sokra nem megyünk, nem árt ha létrehozunk az adott névtéren belül valami osztályt (ami elég morbid lesz, mivel a javascript-ben nincs class keyword, hanem az osztályokat, mint function-öket fogjuk definiálni )
1
2
3
MyNamespace.Translate = function(locale){
this.locale = locale; // ezt felfoghatjuk az osztályunk konstruktorának
}
A fenti osztály viszont példányosítás nélkül mit sem ér, tehát akkor hozzuk létre:
1
2
3
4
var trans = new MyNamespace.Translate("hu_HU"); // példányosítottuk és átadtuk neki a locale értékét, aztán letároltuk az osztályt a trans változóba
console.log(trans.locale); // "hu_HU"
Na de akkor most jöjjön az ami miatt a legtöbben felhagynak a komplexebb javascript kódok írásával, a macera. Hozzunk létre egy függvényt az adott osztály alatt!
1
2
3
4
5
6
7
MyNamespace.Translate.trans = function(key) {
// ide jön valami roppant frappáns fordítási logika
return value;
}
var trans = new MyNamespace.Translate("hu_HU");
console.log(trans.trans(key)); // ide jönne a value, ugye?
Na most amikor a fenti kódot lefuttatjuk, akkor bizony szomorúan kell tapasztaljuk, hogy a programunk bizony csecsre hibára fut. A gond ott van, hogy a javascript másképp kezeli az objektumokat, mint azt pl. Javaban, PHP-ben megszoktuk. A fent leírt Mynamespace.Translate.trans() egy statikus metódusnak minősül, tehát amikor egy példányon akarjuk meghívni, akkor bizony nem találja a rendszer. Ahhoz, hogy ne statikus legyen és ezen a példányon megtalálja, ahhoz az adott objektum ún. prototype-ját kell megb*szatnunk piszkáljuk.
Minden javascriptben példányosított objektum a példányosításkor megnézi a saját prototype-ját és az esetleges általunk beregisztrált módosításokat figyelembe véve fogja példányosítani.
Tehát a megoldás a következő lesz:
1
2
3
4
MyNamespace.Translate.prototype.trans = function(key) { // a prototype objektumba elhelyezzük a mi kis bónuszunkat, így a példányosított osztályban már ott lesz a trans() metódus. Ez igaz a this context-ben meghívottakra is.
// ide jön valami roppant frappáns fordítási logika
return value;
}
Ezzel ugye nem csak az egyes metódusait, de példányváltozókat is beregisztrálhatunk, értéket rendelhetünk hozzá, stb. Nem a legelegánsabb megoldás, de jelen körülmények közt ezzel kell beérjük.
This == ?
Az iménti kódból kimaradt az a jóféle fordítási logika amit itt orvosolni fogunk. Az adott fordítási kulcs/ érték párok egy tömbben lesznek eltárolva, amin mi egy jól bevált forEach-el szimplán menjünk végig keresve az “igazit”.
1
2
3
4
5
6
7
8
MyNamespace.Translate.prototype.trans = function(key) {
Mynamespace.Translate.keys.forEach(function(elem){ // végigiterálunk egy objektumokkal teli tömbön
if(elem.key == key) // ha megvan az elem
this.value = elem.value;
});
}
Na most a fenti kód nekünk nem lesz jó, ugyanis a callback function scopejában létrehozott változó nem fog megjelenni a return-ben, ugyanis az nem a mi osztályunk this-ére vonatkozik, hanem a z adott tömbre.
Ilyen esetekben létrehozhatunk egy változót, ami az osztálypéldányunkra mutat és a foreach callbacken belül használhatjuk ezt.
1
2
3
4
5
6
7
8
9
10
MyNamespace.Translate.prototype.trans = function(key) {
var self = this; // az osztályunk referenciáját egy globális változóba tesszük
Mynamespace.Translate.keys.forEach(function(elem){ // végigiterálunk egy objektumokkal teli tömbön
if(elem.key == key) // ha megvan az elem
self.value = elem.value; // a self mivel átkerült a globális scope-ba, ezért itt is elérjük és rajta keresztül a Translate osztályunknak tudunk példányváltozót beállítani
});
}
Öröklődés
Aki elég beteg, az eljutott eddig a cikkben, annak már valószínűleg nem fogja megülni a gyomrát amikor azt mondom, igen, a javascriptben is lehet ilyet, persze a már jól megszokott sajtreszelővel r*jszolós módszerrel.
Az alábbi példában létrehozok egy kaja osztályt, aztán abból leszármaztatok egy gyros osztályt (nem, nem vagyok éhes).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var kaja = function(kaloria) {
this.kaloria = kaloria; // ez itt a konstruktorunk ugye
}
var kaja.prototype.zaba = function() {
console.log("Épp most zabáltál fel " + this.kaloria + " kalóriának megfelelő kaját!");
// itt ugye a kaja alatt hozunk létre egy metódust, tehát elérjük a kaja osztályunk this context-jét
}
var gyros = function(pitaban) {
kaja.call(this, 500 ) // a call metódussal tudjuk meghívni a "szülő" konstruktorát, amolyan parent::__construct(). Mivel a hívás során átadjuk az objektumunk referenciáját, ezért a this context-ben fog meghívódni.
this.pitaban = pitaban; // a boolean pitában kérdés, amit olyan gyakran hallunk
}
// ez a tényleges öröklődés, ahol a kaja prototípusát megörökli a gyros osztályunk.
gyros.prototype = Object.create(kaja.prototype);
// mivel a kaja osztályunknak is van constructor-a, ezért ezt vissza kell állítsuk, mielőtt példányosítanánk, mert az imént ugye felülírtuk
gyros.prototype.constructor = gyros;
var jofeleGyros = new gyros(true); // példányosítjuk a gyros osztályt
// majd leellenőrízzük, hogy tényleg végbement-e az öröklőgés
console.log(jofeleGyros instanceof gyros); // true
console.log(jofeleGyros instanceof kaja); // true
// és ellenőrízzük, hogy az örökölt metódust is megkaptuk-e
jofeleGyros.zaba(); // Épp most zabáltál fel 500 kalóriának megfelelő kaját!
Ha a prototype kulcsszótól már herótunk van és nem akarjuk hosszan definiálgatni a dolgot, megoldhatjuk ezt úgy is, hogy minden alkalommal prototype objektumokat adunk át:
1
2
3
4
5
gyros.prototype = {
constructor: gyros, // átadjuk ugye a constructorunkat, nehogy elvesszen
zaba : function(kaloria) { // mennyivel jobb, szárazabb érzés, ugye?
}
}
Első körben ennyit szerettem volna kitárgyalni a JS-ről azoknak, akik még nem nagyon mélyedtek el benne, legközelebb végignézzük az egyes tervezési mintákat itt is.