A JavaScript metaprogramozása
Kedves olvasó! Az a célom e cikkel, hogy egy picit elkalandozzunk a mindennapok feladat orientált kódjától, és a JavaScriptet egy másik oldaláról nézzük meg. Hogy tűnődjünk egy kicsit a miérteken, az okokon, összeszedtem gondolatébresztőnek azokat a kérdéseket, melyekbe jómagam nap mint nap belebotlottam, és belebotlok még a mai napig is. Egy olyan összefoglalást próbálok adni, mely szétszórva megtalálható a weben, mégis így összegyűjtve talán nem sok helyen látjuk. Mivel nem szigorúan egy témáról van szó, és uncsi is sokat olvasni, csapongani fogunk ide-oda, és az is biztos, hogy bár egy ilyen cikk megírásakor az ember utánanéz dolgoknak, azért választ mindenre nem fogunk találni.
Megpróbáltam az egyszerűbb dolgokkal indulni, és a bonyolultabbak felé haladni. Ha használod a Firefox Firebug nevezetű hasznos kis kiegészítőjét, akkor bátran ollózd bele a példákat, és játszadozz velük. Legtöbbjük elméleti, bár igyekeztem pár életszerűbb esetet közéjük csempészni. Szintén elő fog tűnni pár jQuery által támogatott példa is, mely nem a reklám helye, de biztosan rajtam kívül is sokan használják még.Persze ez addig jó, míg biztosak vagyunk benne hogy a tömb nem tartalmazza a hat hamis érték egyikét, hisz akkor a ciklus hamarabb befejeződik.
Teljesen jó dolog például ezért ellenőrizni, hogy jQuery-vel létre kell-e hoznom az adott elemet:Ám képzeljünk el egy menüt, melyben a végső menüpontoknak (melyeknek már nincs almenüje) 0 végű id-t adunk, és ezt trükkös JavaScript tudásunkkal kihámoztuk, mert célunk az, hogy fenti csodás popupunkat megjelenítsük, ám csak akkor, ha a kattintott elem id-jének vége 1. Nyilván az alábbi kód nem jQuery, de így csak másolás–kivágás Firebugba, és látjuk hogy nem csalás, nem ámítás!Popupunk így mindig meg fog jelenni, hisz a
Ha már konverziónál tartunk, érdemes megemlíteni aesetét, ahol az első összehasonlításnál történik típuskonverzió, míg a másodiknál nem, tehát a második kifejezésben az összehasonlított értékek típusának és értékének is meg kell egyeznie. Így az első kifejezés visszatérési értéke igaz, a másodiké bizony hamis.
Ha már a hamisoknál tartunk, láthatunk a kódokban egy elegáns, de sokszor zavarba ejtő megoldást:Itt két dolog összejátszása történik. Az egyik a lustaság. Logikai kifejezések kiértékelésekor, ha végeredmény már egyértelmű, a JavaScript nem halad tovább a kifejezésben. Tehát egy
Mielőtt egered elkalandozna a jobb felső sarokba, gyorsan illesszük be az alábbi kódrészletet a Firebugba! Mit is tapasztalunk?Ugye teljes káosz!? A fent említett öt elemi típus közül négy még ok, de a
Kis fényt vihetünk az éjszakába az alábbi kódrészlettel:Ugye ez már megnyugtatóbb. Amit ebből érdemes leszűrnünk az az, hogy a
Nézzük is meg eme két adattípusunkat együtt! Sőt! Legyen három!Lépjünk tovább, és mondjuk azt hogy az objektum olyan összetett adattípus, amely tulajdonságokat (property) tartalmaz. Ha az adott tulajdonság végrehajtható, akkor azt mondjuk hogy függvény. Ha az adott tulajdonság egy szám, akkor azt mondjuk, hogy index, és az adatstruktúra egy tömb. Egyéb esetben a tulajdonságra azt mondjuk hogy… nos… tulajdonság, és a kérdéses elem a szeretett objektumunk!
Ám a JavaScript nem diszkriminál. Mi se tegyük, nevezzük hát az adatot tartalmazó változót tulajdonságnak, és ami tárolja, legyen objektum.
Érdekes tulajdonsága a JavaScriptnek, hogy megengedi a már létrehozott objektumunk későbbi, dinamikus változtatását. Így:Igazából ez számunkra két ok miatt lényeges. Először is a JavaScriptben az értékadások során az objektumok referencia alapján adódnak át, tehát a fenti Ugye-ugye. Láthatjuk, hogy akármit adunk értékül a másolt objektum tulajdonságának, az az eredetiben is elérhetővé válik. Ez jó a memóriának, ám lehet hogy nem jó nekünk. Még mielőtt lemetaprogramozzuk ezt a problémát, csempésztem a fenti példába egy
Tehát lássuk első metafüggvényünket. Mivel a jQuery is tartalmaz egy hasonló függvénytMint a tesztből is látjuk, új objektumunk már új helyet foglal a memóriában, és kedvünkre változtathatjuk, az eredeti mégsem változik. Mit látunk még a függvényből. Láthatjuk hogy a függvény rekurzív, a második paraméterre igazából csak ezért van szükség. Nézzük csak meg közelebbről ezt a részt:Ha már metaprogramozás, szűkítsük be ezt egy sorba, hogy sokkal elegánsabb (és olvashatatlanabb :-)) legyen:Komolyabbra fordítva a szót, mi lehet az a
Íme a következő kód:Megint kitűnik a hasonlóság. Mindegyik objektumunknak van
Akkor ha a függvények objektumok, és a konstruktorok függvények, akkor a konstruktoroknak is van konstruktor tulajdonságuk? Fú, de logikusak vagyunk! És való igaz, van nekik: mind a
Elméleti bla-bla! A lényeg: A konstruktor függvények is objektumok, és ha akarjuk, módosíthatjuk is őket – bár ennek veszélyei is vannak. Ez az, amit a Prototype függvénykönyvtár tesz (legalábbis remélem, bevallom jómagam még sosem használtam :-)). Mi is teszünk majd egy próbát, ám előtte boncolgassuk még ezt az öröklődéses akármit.
A
Ha egyszerű függvényhíváskor a függvényben vanEz lényeges pont. Az
A másik eset, mikor a függvénytMint látjuk, ha a Tehát a konstruktor függvények is csak egyszerű függvények, különbség csak annyi, hogy illik őket nagybetűvel kezdeni. A
És ha kihagyjuk aNahát, most szótárunk egész ügyesen beszél. Ha egy függvénynek nem adunk visszatérési értéket,
Pedig tulajdonítottuk, csak nem tudunk róla. Hisz mindig van egy nagyobb hal: böngészőnk esetében ez aA
Akkor metaprogramozzunk ebből is valamit! Két kívánságunk lehet.
1. Mindenképpen konstruktor függvény legyen függvényünk,
Ezen könnyen tudunk segíteni: mivel aÉs jól működik, éljen! Így létrehozhatunk objektumot az „old school” módon, ahogy régi objektumorientált őseink is tették, és ha a
2. A másik ötletünk nem is a mi ötletünk, hanem ellestük:Nahát, újabb talány. Azt tudjuk, hogy a
Hogy is volt, mikor van normál hívás? Ha aJobb lenne ha az
Most már csak csak az árnyékolhatja be dicsőségünket, hogy minden új objektum létrehozásakor a memóriánkban újabb hely kerül lefoglalásra. Ez az adott objektumra jellemző egyedi tulajdonságoknál nem is baj, de mi a helyzet a függvényekkel? Ezekből elég lenne egy, ám a fenti módszerrel minden új objektumban új függvénypéldány születik. Szerencsére a konstruktor függvényünk (és persze minden függvény) rendelkezik egy tulajdonsággal, melybe beledobálhatjuk az újrahasznosítható elemeket. Ez aÚjrahasznosítható kód? Dobjuk a
A
AKis nyelvtani hibától eltekintve kiderül, hogy bár a Tehát a már meglévő objektumunk is megkapja az új tulajdonságot, ha módosítjuk a konstruktor függvény
Ekkortájt már felüthette fejét a gondolat, hogy vajon a beépített konstruktorok
Mivel aKemény, nem? Ezen a példán egy napig rágódtam, hogy mi is lenne az öröklődés absztrakciójához illő tudományos akármi. Jó, hát nem nagyon sikerült megtalálni a megfelelő alanyt, de legalább kis kutyusok aranyos képe lebeg előttünk!
Tehát lássuk csak. A
Zárásként tehát annyit, hogy ha valami olyan projekten dolgozunk, ami öröklődést kíván, akkor azt konstruktor függvényekkel és azok
Mik nem kerültek ide? Értékül adhatnánk csak az ős prototype objektumát az utód prototype objektumának és így megspórolnánk egy új objektum létrehozását. Köztes függvényt (temporary constructor) használhatnánk, mellyel orvosolnánk az előbbi értékadásnál ama problémát, hogy az utód objektum prototype változása magával vonná az ős objektum prototype változását (hiszen referencia, emlékszünk). Az egész öröklődéses mindenséget függvénybe tehetnénk. Többszörös öröklődést csinálhatnánk, egy utód örökölne több őstől. Létrehozhatnánk olyan mintát, melynél, az utódban hívott függvény megnézi hogy van-e azonos nevű függvény az ősben, és ha van, meghívja azt, majd dolgozik az eredménnyel. És még van, amihez már annyira nem értek, hogy le se merem írni ide. Esetleg ha van olyan, aki nagy projektekben dolgozik, és megosztja velünk tapasztalatait, annak nagyon örülnék, mert egy életszagú példa igazán érdekes lehet.
Nos, hagyjuk is magára a barikat. Bár már az út végén járunk, nézzünk egy kicsit vissza, azokhoz az objektumokhoz, melyeket úgy hívunk: függvények.
A furcsaság akkor kezdődik, mikor egy függvényen belül deklarálunk változót. Ugyanis a JavasSriptben a változók érvényességi köre függvény szintű, és nem blokk szintű. Ha a változónkat a függvényben hoztuk létre, ésSzerintem a barik ismét megvilágítják a problémát. Látjuk hogy a függvényen belül a lokális Ugye valamit elrontottunk. Juhászunk kívülről nem elérhető, és ennek az az oka, hogy a
No de vissza a problémánkhoz:És már működik is. Igazából ezt nagyon sokáig lehetne vesézni, a lényeg az, hogy ha egy függvényen belül valamilyen úton módon létrehozunk egy másik függvényt, és azt globálissá tesszük, akkor létrejön a closure. Ez lehet a fenti módon, de képezhet closure-t a visszatérési értékként megadott függvény, sőt a visszatérési értékként megadott objektumban létrehozott függvény.
Pár clousre példát fogunk még látni. Előtte azonban egy picit térjünk vissza a karámhoz.
Ahhoz hogy változóink létrejöjjenek, az őket tartalmazó függvényt egyszer meg kell hívni. Ez történik aLátjuk, hogy függvények paraméter átadásánál teljesen mindegy hogy az objektumot magát adom át, vagy az őt tartalmazó változót. Na jó, a változók számának szempontjából persze nem mindegy. Mivel a függvények objektumok, ezért:Tehát átadjuk az adatot, vagy az adatot tartalmazó változót, utánabiggyesztjük a „hajtódj végre”
Ezen tudásunkkal felvértezve visszatérhetünk a closure példánkhoz, és azt mondjuk: nincs karám!És barink körömtelen! Ám akik igazán éles szeműek, azoknak feltűnik még egy zárójelpáros, melybe bezártuk anonymus függvényünket. Ennek oka hogy a fenti módszer csak akkor működik, ha függvényünkkel „kifejezést” (function expression) képezünk, ellentétben a zárójel nélküli deklarációval (function declaration) ami leginkább a módú függvénylétrehozás, mely szintén kifejezést képez függvényünkből, és ezért itt nem is kell a két másik zárójel. Aki jobban kíváncsi a témára, bátran lapozza fel a Mozilla Developer Centert.
Természetesen a végrehajtó zárójelpárosban megadhatjuk a paramétereket is, melyet névtelen függvényünk vár – ha vár. Nos, tehát végül a closure zárásaként írjunk egy getter–setter metóduspárost, mellyel privát változónkat érhetjük el és állíthatjuk be:A getter–setter metódusok nagyon jól használhatók olyan esetben, ha többen dolgozunk egy projekten, és az általunk történő megvalósítást el akarjuk rejteni a többi kód elől, így egy biztonságos, egységes, esetleg egyszerűbb működést akarunk biztosítani programozó barátunknak. Ám azt mindig gondoljuk át, hogy megéri e ilyenformán írni a kódunkat, és nem esünk-e át véletlenül a ló túloldalára, és évek múltán mink, esetleg más programozó órákat meredhet üres szemmel a monitor előtt azon gondolkozva, hogy ez a referenciára referáló referencia mi a csuda is akart lenni, comment ide vagy oda.
Nos, belepillantottunk hogyan is működik a closure, láttuk, hogy az általunk függvénynek tisztelt objektumot paraméterként át tudjuk adni, meg tudjuk hívni újra, sőt ha anonymous függvényünket kifejezésként hoztuk létre, azonnal meg is hívhatjuk. És függvényeink eszköztára még mindig nem merült ki, szemezgessünk még pár dolgot:Íme. Megvizsgáljuk, hogy a tényleges paraméterszám nagyobb-e a várt paraméterszámnál, és ha igen, újra hívjuk függvényünket kevesebb paraméterrel. Az Ezzel a módszerrel akár előre beállított paramétereket is megadhatunk függvényünkben, és módosíthatjuk azokat, ha a pillanatnyi helyzet úgy kívánja.
Tegyük fel hogy meg akarjuk számolni vajon hány linket hoztunk már létre (esetleg dinamikus id létrehozás miatt, vagy ki tudja miért). Ilyen esetben is jól jöhet azhelyettÍgy egy globális változóval kevesebb.
Előfordulhat olyan eset, hogy egy függvényt más környezetben szeretnénk meghívni, mint amelyhez eredetileg tartozik. Tehát a A
Persze nem csak kölcsönvehetjük, hanem mint már többször is utaltunk rá, módosíthatjuk is a konstruktor függvényekAz első függvény egy kis átvezető az előző példánkból, ugyanis itt kölcsönvesszük az
A második függvénnyel az
Gyakorlatilag az elmélet végére értünk. Utolsó fejezet jogán még megemlítünk pár dolgot, mely igazából az eddig átnézettek variálása, talán gyakorlatiasabb szemszögből.Hát igen, ez elég zavaró. Ilyenkor segíthet egy namespace:Persze kedvünk szerint adhatunk a fő objektumunknak rövidebb, hosszabb nevet, mint bizonyára ismerjük a szeretett függvénykönyvtárunk elején lévő pár sort:…illetve pár sorral lejjebb…Olyan jó dolog a lustaság! Mily egyszerű dolog egy így változóknak értéket adni. A JavaScript szerencsére több szempontból támogatja a lustaságot, például mikor függvényünknek a böngészők közötti inkompatibilitásnak (és van ilyen szó!) kell megfelelni.Ugye itt már semmi új nincs nékünk. Mivel tudjuk, hogy a függvények adatok, nyugodtan értékül adhatjuk őket változóknak, még akkor is, ha magát a függvényt tartalmazó változót írjuk felül. Így hát mikor először meghívjuk a függvényt, az ellenőrzi melyik metódus támogatott az adott böngészőben (feature sniffing), és az eredmény szerint felülírja magát. Majd a függvény végén egy hívással végre is hajtja a már új igényeknek megfelelően definiált önmagát. Minden egyes további hívással már az újradefiniált függvény fog végrehajtódni, mely a böngészőnek megfelelő kódot tartalmazza. Azért hívjuk lazy definition-nak, mert lustasága abban merül ki, hogy ha egyszer sem hívjuk a függvényt, akkor semmi sem történik, tehát csak akkor dolgozik ha igazán szükség van rá. Elődjeként ismert az init time branching, mely mint neve is sugallja, egyszer mindenképpen lefut, és ekkor szaglássza ki az épp aktuálisan támogatott metódusokat. A szaglászásról még annyit, hogy érdemes megvizsgálni minden általunk használni kívánt metódus böngésző általi támogatását, mert nem biztos hogy ha az egyik működik (pl.
Tehát ki akarunk választani egy vagy több HTML elemet, mondjuk ID alapján. Ezeket tárolnunk kell valahogy, legyen hát egy tömb. Az elemeket visszaadó függvényt védjük meg, legyen privát, kell nekünk egy closure, és egy önmagát végrehajtó függvény. Végül, mivel valamiben tárolni kell mindezt, és majd valamihez kapcsolni kell a metódusainkat, legyen a visszatérési érték objektum. Kell hát aÍgy hát eddig meg is vagyunk. A
Akkor érjük hát el valahogy ezt a tömböt. Mivel újrahasznosítható kódról van szó, beugrik a prototype, mely minden konstruktor függvénynek sajátja, és privát függvényünknél is kiválóan fog működni. Ami pedig a láncolást illeti, nagyon egyszerű. Mivel azt szeretnénk, hogy a metódusaink az eredeti objektum módisított tömbelemein dolgozzanak, adjuk nekik mindig oda az eredeti objektumot. Magyarán, a láncban előbb levő függvény térjen vissza az objektumra való referenciával:
Nézzünk is:És íme, primitív kis könyvtárunk elkészült. Szaladjunk hát gyorsan végig rajta! Először is látjuk, hogy a
Az
Az
A
Az utolsó négy függvényben feltűnik egy kis új elem – a
Ki is próbálhatjuk egy frissiben összedobott template oldalon valahogy így:És működik is, bizony. Azért mielőtt egy könnycseppet elmorzsolnánk a szemünk sarkában, és az este maradék részét a jQuery függvénykönyvtár újraírásával töltenénk, inkább kattintsunk a honlapjukon a Download gombra, mert ez a kereket már feltalálták. Ha belepillantunk néha–néha a kódba, az maga a metaprogramozás.
Hát ennyi volna. Nem is tudom mivel lehetne lezárni egy ilyen cikket, de remélem hogy pár új dolgot sikerült megismertetni veled kedves olvasó. Bocsánat a baris példákért, a hosszú körmondatokért, és a néha (remélem csak néha!) gyenge poénokért.
■ Megpróbáltam az egyszerűbb dolgokkal indulni, és a bonyolultabbak felé haladni. Ha használod a Firefox Firebug nevezetű hasznos kis kiegészítőjét, akkor bátran ollózd bele a példákat, és játszadozz velük. Legtöbbjük elméleti, bár igyekeztem pár életszerűbb esetet közéjük csempészni. Szintén elő fog tűnni pár jQuery által támogatott példa is, mely nem a reklám helye, de biztosan rajtam kívül is sokan használják még.
Hol is kezdjük… metaprogramozás?
Mielőtt már az elején fogalmi dugóba keverednénk, a sok metaprogramozás definíció közül a cikkben metaprogramozás a programnyelv (itt JavaScript) mechanikájának, működésnek manipulálását jelenti, és célja nem más, mint hogy kódunk jobban és könnyebben újrahasználható, hordozható, olvasható, kezelhető, változtatható, alkalmazkodó legyen. Olyan fogalmak lebegjenek szemeink előtt, mint az öröklődés, adatrejtés, interfészek létrehozása, speciális függvények írása stb. Szintén itt jegyzem meg, hogy az angol fogalmakat nem igazán áll szándékomban lefordítani, inkább a mögöttes tartalmat próbálom majd megvilágítani, több kevesebb sikerrel.A hat kis hamis
Maradjunk abban, hogy láttunk már olyat, aki deklarált már változót, írtfor
ciklust, és túl van a „helló világ” programján. Ha mégsem ez a helyzet, akkor pillants kérlek a W3Schools JavaScript rovatára. Ám ha már a változóknál tartunk, érdemes megemlíteni a hat kis hamist. A JavaScriptben hamisra (false) értékelődik ki az üres string ""
, a null
, az undefined
, a 0
értékű szám, a NaN
értékű szám, valamint (nahát!) a logikai false
. Az összes többi érték (beleértve az üres objektumot vagy a "0"
stringet) igazat ad nekünk a logikai kifejezésekben. Ezt a tudásunkat felhasználhatjuk vicces for
ciklusok írásakor:
var tomb = [1, 2, 3, 4, 5];
for (var j = 0; tomb[j]; j++) {
console.log(tomb[j]);
}
Teljesen jó dolog például ezért ellenőrizni, hogy jQuery-vel létre kell-e hoznom az adott elemet:
if (!$('div#popup').length) {
$('<div id="popup">Poppp!</div>').appendTo('body').hide();
}
var kattintott_id = "menu_10_0"; // itt lenne ilyesmi, hogy $(this).attr('id');
var vegso_menupont_e = kattintott_id.substr(kattintott_id.lastIndexOf('_') + 1, kattintott_id.length);
if (vegso_menupont_e) {
console.log('mindig poppanok, pedig az érték: ' + vegso_menupont_e);
}
'0'
string is igaz értéknek számít. Segíthet esetünkben egy konverzió, pl. a parseInt(vegso_menupont_e)
vagy a Number(vegso_menupont_e)
függvények, mely hamiskás (falsy) értékek egyikére alakítják az id-részletet. Ha már konverziónál tartunk, érdemes megemlíteni a
console.log(0 == '0'); // true
console.log(0 === '0'); // false
Ha már a hamisoknál tartunk, láthatunk a kódokban egy elegáns, de sokszor zavarba ejtő megoldást:
var beallitasok;
var alkalmazott_beallitasok = beallitasok || {};
console.log(alkalmazott_beallitasok);
true || false || true
esetén, mivel a végeredmény egyértelmű, az első true
-nál megáll a kiértékelés. Ez jó. A másik dolog a JavaScript azon tulajdonsága, ha egy logikai (boolean) és egy nemlogikai kifejezés kiértékelése történik, akkor a nemlogikai eredményt fogjuk megkapni végül. Tehát a fenti kódban az üres objektumot kapjuk (ami logikailag igaz), az eredetileg logikai (hamiskás) értéknek számító undefined
helyett. Ha a beallitasok
változónak értéket adunk, akkor természetesen az fog helyet kapni az alkalmazott_beallitasok
változóban. És mindenki megnyugszik! Egészen addig jól működik ez a kód, amíg a beallitasok
változóba nem töltjük a hat hamiskás érték egyikét, mert ebben az esetben hiába definiáltuk előre a változónkat (pl. 0
-val vagy ""
-lel), akkor is felül fog íródni, bár már nem undefined
.Az öt elem könyve és az objektumok
Mijamoto Muszasi óta már eltelt pár év, de ő már akkor tudta, amire most mi is rávilágítunk: öt primitív adattípusunk van a JavaScriptben. Ezek: number, string, boolean, undefined, és null. Az összes többi objektum (sőt, egy picit a null is az, de erről később). És ne variáljuk túl. Ennyi. Nincs osztály, nincs tömb, nincs függvény, csak öt primitív adattípus és objektumok. Persze majd lesznek olyan objektumok, melyek natívan úgy működnek mint a tömb, úgy csinálnak, mint a függvények, és új objektumokat hoznak létre, akár az osztályok. Úgy csinálnak, olyanok, de nem azok. Csak öt elemi típus és objektumok. Ilyen egyszerű.Mielőtt egered elkalandozna a jobb felső sarokba, gyorsan illesszük be az alábbi kódrészletet a Firebugba! Mit is tapasztalunk?
var szam = 123;
var logikai = false;
var nem_definialt = undefined;
var semmi = null;
var szoveg = 'Helló Világ!';
var tomb = [];
var objektum = {};
var fuggveny = function () {alert('nohát');};
console.log(typeof szam); // number
console.log(typeof logikai); // boolean
console.log(typeof nem_definialt); // undefined
console.log(typeof semmi); // object (!)
console.log(typeof szoveg); // string
console.log(typeof tomb); // object (!)
console.log(typeof objektum); // object
console.log(typeof fuggveny); // function;
null
-ra azt kapjuk hogy object
, a szeretett tömbünk object
, na jó, a függvény az helyén van, legalább.Kis fényt vihetünk az éjszakába az alábbi kódrészlettel:
console.log(semmi instanceof Object); // false
console.log(tomb instanceof Array); // true
console.log(objektum instanceof Object); // true
console.log(fuggveny instanceof Function); // true
typeof
és az instanceof
operátorok használatával ha nagyon nagy szükségünk van rá, kinyomozhatjuk a kérdéses változó miféle fajta. Erre igazán olyankor érdemes figyelni, mikor tömböt és objektumot fogadunk el a függvényünkkel, de nem akarjuk, hogy működését egy null
tönkretegye. Példát látunk majd erre.Nézzük is meg eme két adattípusunkat együtt! Sőt! Legyen három!
Függvények, tömbök, objektumok, nekem mind. Egy.
Mondjuk, hogy az adat egy olyan valami, amit bele tudok tuszkolni egy változóba. Valahogy így:
var tomb = [1, 2, 3];
var objektum = {egy: 1, ketto: 2, harom: 3};
var fuggveny = function (a, b, c) {console.log(tomb[1] + ' ' + objektum['ketto'])};
Ám a JavaScript nem diszkriminál. Mi se tegyük, nevezzük hát az adatot tartalmazó változót tulajdonságnak, és ami tárolja, legyen objektum.
Érdekes tulajdonsága a JavaScriptnek, hogy megengedi a már létrehozott objektumunk későbbi, dinamikus változtatását. Így:
tomb.tulajdonsag = 'Mi ';
objektum['tulajdonsag'] = 'nagyon ';
var fuggvenytulajdonsag = 'tulajdonsag';
fuggveny[fuggvenytulajdonsag] = 'hasonlítunk.';
console.log(tomb['tulajdonsag'] + objektum.tulajdonsag + fuggveny['tulajdonsag']); // Mi nagyon hasonlítunk.
tomb
, objektum
, fuggveny
másolása során – ha a másolatot módosítjuk – módosulni fog az eredeti is. Erre érdemes figyelni.
var tomb2 = tomb;
var objektum2 = objektum;
var fuggveny2 = fuggveny;
tomb2.push(2);
console.log(tomb); // [1, 2, 3, 2]
objektum2.kiir = function () {
var result = '';
for (var j in this) {
result += j + ': ' + this[j] + ', ';
}
return result;
}
console.log(objektum.kiir()); // egy: 1, ketto: 2, harom: 3, tulajdonsag: nagyon, kiir: function () { var result = ""; for (var i in this) { result += i + ": " + this + " "; } return result; },
delete fuggveny2.tulajdonsag;
console.log(fuggveny.tulajdonsag); // undefined
for in
ciklust, mely mint látjuk kiírja az adott objektum tulajdonságait, legyenek azok bármilyenek. Ám ne feledjük, hogy tömböknél csak az indexelt tulajdonságokkal (tehát a tömb elemeivel) működik. Észrevehetünk még egy this
szócskát is, mely még vissza fog térni a későbbiek során. És persze a második fontos dolog, hogy a JavaScript engedélyezi a dinamikus tulajdonságok létrehozását, majd törlését, akkor és ott ahol csak óhajtjuk.Tehát lássuk első metafüggvényünket. Mivel a jQuery is tartalmaz egy hasonló függvényt
extend()
néven (persze sokkal jobban kiélezve), legyen a mi függvényünk is extend
, így látszik, milyen kreatívak vagyunk.
function extend(p, c)
{
var c = c || {};
for (var j in p) {
if (typeof p[j] === 'object') {
if (p[j] !== null) {
if (p[j].constructor === Array) {
c[j] = [];
} else {
c[j] = {};
}
extend(p[j], c[j]);
}
} else {
c[j] = p[i];
}
}
return c;
}
var vegyes = {
tomb: [1, 2, 3, 4],
objektum: {
fuggveny: function () {alert('nohát');},
tulajdonsag: 'Jól be vagyunk ágyazva!',
}
}
var vegyes2 = extend(vegyes);
vegyes2.tomb.push(5);
console.log(vegyes.tomb); // [1, 2, 3, 4]
console.log(vegyes2.tomb); // [1, 2, 3, 4, 5]
if (p[j].constructor === Array) {
c[j] = [];
} else {
c[j] = {};
}
c[j] = (p[j].constructor === Array) ? [] : {};
constructor
és mi az az Array
? A konstruktor függvények
Mint ahogy már említettük, az objektum egy olyan doboz, melybe különféle tulajdonságokat pakolhatunk. Beszéltünk már arról is, hogy a tömb, a függvény és az objektum mind ilyen „dobozok”. Ám ezek a dobozok már kezdetben sem üresek. Például gondoljunk atomb.length
-re a tomb.push()
-ra, a fuggveny.arguments
-re vagy az objektum.toString()
-re. Ilyen bizony a fenti p[j].constructor
is. Hogy hogyan kerülnek oda ezek a tulajdonságok? Nos, az objektumaink öröklik őket, mégpedig a létrehozás pillanatában. Íme a következő kód:
var objektum_literal = {};
var objektum_konstruktor = new Object();
var tomb_literal = [];
var tomb_konstruktor = new Array(1, 2, 3, 4);
var fuggveny_literal = function () {alert('nohát');}
console.log(objektum_literal.constructor === Object); // true
console.log(objektum_konstruktor.constructor === Object); // true
console.log(tomb_literal.constructor === Array); // true
console.log(tomb_konstruktor.constructor === Array); // true
console.log(fuggveny_literal.constructor === Function); // true
constructor
tulajdonsága, mely nem másra, mint az őt létrehozó konstruktor függvényre mutat. A színfalak mögött ugyanaz történik akár literállal akár konstruktor függvénnyel hozzuk létre az adott objektumot. (Biztos feltűnik, hogy a függvényt nem hoztuk létre konstruktor függvénnyel, lehetne, de nem fogjuk használni.)Akkor ha a függvények objektumok, és a konstruktorok függvények, akkor a konstruktoroknak is van konstruktor tulajdonságuk? Fú, de logikusak vagyunk! És való igaz, van nekik: mind a
Function
-ra mutat.Elméleti bla-bla! A lényeg: A konstruktor függvények is objektumok, és ha akarjuk, módosíthatjuk is őket – bár ennek veszélyei is vannak. Ez az, amit a Prototype függvénykönyvtár tesz (legalábbis remélem, bevallom jómagam még sosem használtam :-)). Mi is teszünk majd egy próbát, ám előtte boncolgassuk még ezt az öröklődéses akármit.
This
, new
A this
egy mutató. Azt jelenti: ez az objektum. De melyik? Vegyünk két lehetőséget.Ha egyszerű függvényhíváskor a függvényben van
this
, akkor az a this
arra az objektumra mutat, melynek tulajdonságaként a függvény hívása megtörténik. Íme:
var objektum_jack = {
nev: 'Jack',
nevem: function () {console.log(this.nev + ' a nevem');}
}
var objektum_joe = {
nev: 'Joe'
}
objektum_joe.nevem = objektum_jack.nevem;
objektum_jack.nevem(); // Jack a nevem
objektum_joe.nevem(); // Joe a nevem
objektum_joe.nevem = objektum_jack.nevem
értékadásnál, mivel a függvények objektumok, csak a referenciát adjuk át. Ám hívásnál már nem az számít, a függvény eredetileg hol lett létrehozva, hanem az, hogy mely objektum tulajdonságaként lett meghíva.A másik eset, mikor a függvényt
new
operátorral hívjuk meg. Ilyet már láttunk, pl. a tomb = new Array(1, 2, 3, 4)
értékadásnál. Ezek a konstruktor függvények. Írjunk mi is ilyet! Ám legyen valami valóságszaga, függvényünk legyen egy szótár!
function Explain(term, description)
{
this.term = term;
this.description = description;
this.speak = function () {console.log('A ' + this.term + ' jelentése: ' + this.description);}
}
var css = new Explain('CSS', 'egymásba hulló stíluslapok');
css.speak(); // A CSS jelentése: egymásba hulló stíluslapok
var closure = new Explain('A closure', 'nem ZÁRLAT');
closure.speak(); // A closure jelentése nem ZÁRLAT
new
operátort használjuk, akkor az értékadás bal oldalán egy új objektum keletkezik, és a konstruktor függvényünkben szereplő this
erre az objektumra fog mutatni. Az új objektum constructor
tulajdonsága kitaláljuk hova mutat? A függvényünkre, bizony.
console.log(css.constructor === Explain); // true
console.log(closure.constructor === Explain); // true
console.log(typeof css === 'object'); // true
console.log(typeof closure === 'object'); // true
new
használata miatt pedig új objektum keletkezik, ezt is bebizonyítottuk az utolsó két sorban. És ha kihagyjuk a
new
-t? Ok, hagyjuk ki!
var visszateres = Explain('undefined', ' egy függvény visszatérési értéke, ha nincs result');
console.log(visszateres); // undefined
visszateres.speak(); // undefined TypeError meg minden csúnyaság
undefined
-dal tér vissza. Halvány reménysugár még pislákol bennünk, mikor megpróbáljuk futtatni a visszateres.speak()
függvényt, ám gyorsan kihuny az is. Akkor a this
most hova mutat? Az első eset az érvényes? Hisz semmilyen objektumnak nem tulajdonítottuk függvényünket.Pedig tulajdonítottuk, csak nem tudunk róla. Hisz mindig van egy nagyobb hal: böngészőnk esetében ez a
window
objektum. Mikor globálisan deklarálunk egy változót (objektumot, tömböt, függvényt, akármit) az a window
objektum tulajdonságává válik.
speak(); // az undefined jelentése: egy függvény...
window.speak(); // az undefined jelentése: egy függvény...
alert('Nahát!');
window.alert('Nahát');
this
így hát vagy arra az objektumra mutat, melynek tulajdonságaként a függvényt meghívtuk (legfelül ez a window
objektum); vagy ha a függvény konstruktor, és new
-val hívjuk, akkor a new
operátor által létrehozott új üres objektumra mutat.Akkor metaprogramozzunk ebből is valamit! Két kívánságunk lehet.
1. Mindenképpen konstruktor függvény legyen függvényünk,
new
-val vagy new
nélkül hozza létre új objektumunkat.Ezen könnyen tudunk segíteni: mivel a
new
operátor új objektumot hoz létre, és nekünk a célunk új objektum létrehozása, adjon vissza függvényünk objektumot, és akkor mindegy lesz hogy az új objektum kétszer jön létre vagy egyszer.
function Explain(term, description)
{
return {
term: term,
description: description,
speak: function () {console.log('A ' + term + ' jelentése: ' + description);}
}
}
var new_szi = new Explain('New', 'új objektumot a memóriába, hagy teljen!');
new_szi.speak(); // A New jelentése...
var _szi = Explain('prototype', 'nemsoká kiderül, és megoldást ad előző problémánkra!');
_szi.speak(); // A prototype jelentése...
new
-t kihagyjuk, akkor sem lesz baj.2. A másik ötletünk nem is a mi ötletünk, hanem ellestük:
var szoveg = '112';
console.log(typeof szoveg); // string
var szam = Number(szoveg);
console.log(typeof szam); // number
var szam_objektum = new Number(112);
console.log(typeof szam_objektum); // object
eredmeny = szam_objektum + 12;
console.log(eredmeny); // 124
Number
egy objektum (na jó, függvény). Több belőle nem lehet, tehát a new
nélkül hívott függvény, mely átalakítja a szoveg
változó értékét számmá, és a new
-val hívott immár konstruktor függvény, mely új számot hoz létre, ugyanaz. A typeof
eredményét látva picit fellélegzünk, azért van ebben logika, lám, a new
tényleg objektumot hozott létre. Ám ezek szerint létezik módszer, mellyel a függvény tudja, hogy van-e előtte new
, vagy nincs? Bizony létezik, és hogy keretes legyen eme költemény, a megoldás ez. Mondom [i]ez. A this
!Hogy is volt, mikor van normál hívás? Ha a
this
értéke arra az objektumra mutat, melynek a hívott függvényünk tulajdonsága. Tehát meg kell néznünk, hogy a this
-ünkben van-e referencia függvényünkre. Erre való az in
operátor.
function Explain(term, description)
{
if ('Explain' in this) {
// normál hívás
console.log('A ' + term + ' és ' + description + ' páros kiíratása');
} else {
// hívás new-val
this.term = term;
this.description = description;
this.speak = function () {console.log('A ' + this.term + ' jelentése: ' + this.description);};
}
}
Explain('Java', 'Script'); // A java és a script páros kiíratása
var objektum = {};
objektum.Explain = Explain;
objektum.Explain('A j', 'Query'); // A j és a query...
var uj = new Explain('A prototype', 'lassan kiderül');
uj.speak(); //A prototype jelentése...
'Explain'
string helyett magát a referenciát tudnánk vizsgálni, de az in
operátor sajnos stringet fogad el a bal oldalán. Ha nem este 10 óra lenne, biztos kigondolnék valami jó kis metamegoldást :-) Igazából konstruktor függvényként valószínű hogy nem fogjuk értékül adni magát a függvényt, így talán elég is ez az egyszerű vizsgálat.Most már csak csak az árnyékolhatja be dicsőségünket, hogy minden új objektum létrehozásakor a memóriánkban újabb hely kerül lefoglalásra. Ez az adott objektumra jellemző egyedi tulajdonságoknál nem is baj, de mi a helyzet a függvényekkel? Ezekből elég lenne egy, ám a fenti módszerrel minden új objektumban új függvénypéldány születik. Szerencsére a konstruktor függvényünk (és persze minden függvény) rendelkezik egy tulajdonsággal, melybe beledobálhatjuk az újrahasznosítható elemeket. Ez a
prototype
.Újrahasznosítható kód? Dobjuk a prototype
-ba!
A prototype
és az öröklődés fogalomköre olyan szinten ki van vesézve, hogy meg sem kísérlem átfogni az egészet. Pár számomra szimpatikus momentumot említek csak meg itt.A
prototype
tulajdonsággal azon JavaScript objektumok rendelkeznek, melyeket mi függvényekként ismerünk. A prototype
objektumban levő tulajdonságok nem magára a függvényre vannak hatással, hanem a függvény által létrehozott új objektumokra. Egyszerűen a prototype
objektumban létrehozott tulajdonság elérhető lesz minden új objektumban, melyet a függvénnyel létrehoztunk. Mivel csak referenciaként él új objektumunkban, a prototype
használata igen gazdaságos a memória szempontjából.
function Objektumok(nev, konstruktor)
{
this.nev = nev;
this.konstruktor = konstruktor;
}
Objektumok.prototype = {
csoport: ' objektum ',
kiir: function () {console.log('A ' + this.nev + ' ' + this.csoport + ', konstruktor függvényének neve: ' + this.konstruktor);}
}
var tomb = new Objektumok('tömb', 'Array');
var objektum = new Objektumok('objektum', 'Object');
var fuggveny = new Objektumok('függvény', 'Function');
tomb.kiir(); // A tömb objektum...
objektum.kiir(); // A objektum objektum...
fuggveny.kiir(); // A függvény objektum...
tomb
, objektum
, fuggveny
változóknak nincs kiir
tulajdonságuk, mégis elérik a konstruktor függvény prototype
objektumából, ahogy a csoport tulajdonságot is. Vegyük észre hogy a this
ugyanúgy él függvényünkben mint eddig, egyszerűen ha a keresett elem nincs az objektumban, az értelmező megnézi a prototype
-ban. És mivel a prototype
referencia, teljesen dinamikus objektumként működik:
console.log(tomb.nev); // Tömb
Objektumok.prototype.semmi = function (nev) {
this.nev = nev;
console.log('A ' + this.nev + ' nem null, bár a null is ' + this.csoport)};
tomb.semmi('Az üres tömb'); // Az üres tömb nem null, bár a...
console.log(tomb.nev); // Üres tömb
prototype
-ját. Annyit még jegyezzünk meg, hogy a saját tulajdonságok mindig felülírják az azonos nevű prototype
tulajdonságokat.Ekkortájt már felüthette fejét a gondolat, hogy vajon a beépített konstruktorok
prototype
-ját is megváltoztathatjuk? Meg bizony, bár erről erősen megoszlanak a vélemények, és én magam a „ne változtassuk” oldalon állok. Olyan szépek így. Ám mivel metaprogramozunk, ígérem, változtatunk egyet, hogy lássuk, tényleg működik.Mivel a
prototype
egy objektum, miért ne írhatnánk felül egy konstruktor függvénnyel? Így annak is lenne prototype
-ja és annak is lenne prototype
-ja és igen kisfiam, már az üküküknagyapád is megette a spenótot. Ilyen van: a prototype chain. Ekkor, ha az értelmező nem találja a keresett tulajdonságot az objektumban, megnézi annak prototype
-ját, majd annak prototype
-ját és végül az Object
prototype
-ját, mikor is bátran bejelenti, hogy a keresett tulajdonság nem létezik.
function Farkas() {
}
Farkas.prototype={
fajta:'Farkas',
labak:'négy',
farok:'egy',
hang:'Voúúúúúú Voúúúúú',
beszel:function(){console.log(' A '+this.fajta+' hangja: '+this.hang+'. A '+this.fajta+'nak '+this.labak+' lába és '+this.farok+' farka van')}
}
function Kutya(fajta_nev) {
this.fajta=fajta_nev;
};
Kutya.prototype=new Farkas();
Kutya.prototype.constructor=Kutya;
Kutya.prototype.hang='Vau Vau';
function JuhaszKutya(nev) {
this.nev=nev
}
JuhaszKutya.prototype=new Kutya('Puli');
JuhaszKutya.prototype.constructor=JuhaszKutya;
JuhaszKutya.prototype.terel=function(){console.log('Az én nevem '+this.nev+', fajtám '+this.fajta+' és azt mondom '+this.hang+' menjetek barik a karámba!');};
var akela=new Farkas();
akela.beszel(); //A Farkas hangja...
var fifi=new Kutya('Tacskó');
fifi.beszel(); //A Tacskó hangja...
var bogancs=new JuhaszKutya('Bogáncs');
bogancs.beszel(); //A Puli hangja....
bogancs.terel(); //Az én nevem Bogáncs...
Tehát lássuk csak. A
Farkas
a legfelső ős, nincs is neki saját tulajdonsága. A prototype
objektum viszont tartalmazza ami nekünk kell: a fajta (jó, tudom hogy faj, de a példa kedvéért…), a lábak és a farok számát, valamint egy függvényt, mellyel ősünk meg is szólal. Ki is próbálhatjuk, akela
be is jelenti mire képes. A Kutya
konstruktornak már van saját tulajdonsága, a fajta. Prototype
objektumát teljesen felülírjuk, így megkapja a Farkas
összes tulajdonságát. Mivel a constructor
ilyen esetben (ha a prototype
-ot teljesen felülírjuk) megzavarodik, ezért helyesen beállítjuk. Majd a kész prototype
hang tulajdonságán változtatunk, hisz kutyusunk már ugat. És lám, fifi
máris kész tacsi! A Farkas prototype
objektumában levő fajta
tulajdonság pedig szépen felülíródott a saját fajta
tulajdonsággal, a Farkas.prototype.hang
pedig nem kerül elérésre, hisz a Kutya prototype
objektumában is van ilyen tulajdonság, és az értelmező itt meg is áll. A JuhaszKutya
öröklődése ugyanaz mint a Kutya
estében, és kap egy saját tulajdonságot, mely a nev
. A prototype
objektuma kap egy terel
metódust, ám látjuk, hogy a beszel
tulajdonságot is tökéletesen elérjük.Zárásként tehát annyit, hogy ha valami olyan projekten dolgozunk, ami öröklődést kíván, akkor azt konstruktor függvényekkel és azok
prototype
objektumával megvalósíthatjuk. Az őröklendő tulajdonságokat rakjuk a prototype
-ba, és ha teljesen új objektummal írjuk felül a prototype
-ot (nem csak „augmenting” történik, tehát új tulajdonságok hozzáadása a már létezőkhöz), akkor a prototype.constructor
tulajdonságot állítsuk a helyes értékre. Azt sem feledjük, hogy a prototype
„él”, tehát változtatása magával vonja már meglévő objektumaink változását is.Mik nem kerültek ide? Értékül adhatnánk csak az ős prototype objektumát az utód prototype objektumának és így megspórolnánk egy új objektum létrehozását. Köztes függvényt (temporary constructor) használhatnánk, mellyel orvosolnánk az előbbi értékadásnál ama problémát, hogy az utód objektum prototype változása magával vonná az ős objektum prototype változását (hiszen referencia, emlékszünk). Az egész öröklődéses mindenséget függvénybe tehetnénk. Többszörös öröklődést csinálhatnánk, egy utód örökölne több őstől. Létrehozhatnánk olyan mintát, melynél, az utódban hívott függvény megnézi hogy van-e azonos nevű függvény az ősben, és ha van, meghívja azt, majd dolgozik az eredménnyel. És még van, amihez már annyira nem értek, hogy le se merem írni ide. Esetleg ha van olyan, aki nagy projektekben dolgozik, és megosztja velünk tapasztalatait, annak nagyon örülnék, mert egy életszagú példa igazán érdekes lehet.
Nos, hagyjuk is magára a barikat. Bár már az út végén járunk, nézzünk egy kicsit vissza, azokhoz az objektumokhoz, melyeket úgy hívunk: függvények.
Függvények és változók
Hogy végleg kiteljesítsük metaprogramozói tudásunkat, egy kicsit vissza kell térnünk még a gyökerekhez. Ha változót deklarálunk, akkor nem árt mindig avar
kulcsszóval kezdeni. Ha a var
kulcsszó elmarad, akkor változónk érvényességi köre globális lesz, azaz változónk a window
objektumnak lesz tulajdonsága.A furcsaság akkor kezdődik, mikor egy függvényen belül deklarálunk változót. Ugyanis a JavasSriptben a változók érvényességi köre függvény szintű, és nem blokk szintű. Ha a változónkat a függvényben hoztuk létre, és
var
kulcsszónkat nem felejtettük el, akkor a függvényen kívülről változónk elérhetetlen lesz. Ha pedig függvényünkön kívül létezik egy ugyanolyan nevű változó, akkor is függvényünkben levő párja fog érvényesülni, még akkor is, ha esetleges rá való hivatkozás (mely szintén függvényen belül van természetesen) hamarabb történik mint a (szintén függvényen belüli) értékadás. Lehet kicsit tömény, de én értem, éljen :-)
var bari = 'Beeee';
function karam()
{
console.log(bari) //undefined
var bari = 'Karámba beee'; //itt kap értéket, de az érvényessége már előbb él!
var fekete_bari = 'Beee Beee';
juhasz = 'Én kilátszom a karámból';
}
karam(); //undefined
console.log(bari); //'Beee'
console.log(typeof fekete_bari); //undefined
console.log(juhasz); //én kilátszom a karámból
bari
változó már él, bár értéket csak később kap. Kívülről a fekete_bari
változónk egyáltalán nem látszik, hisz szintén lokális, és a var
nélkül létrehozott juhasz
kívülröl is tökéletesen látszik. Gondoljuk tovább ezt a juhász bácsit. Legyen ő valamivel különlegesebb, ne csak egyszerű adat, hanem egy meghívható adat. Kiabáljunk neki kívülről: „Bátyám, ugyan körmölje mán mög azt a fekete birkát!” Juhász bácsi készségesen meg fogja körmölni, és így születik a:Closure
Az a tény, hogyvar
nélkül létrehozott változónk mindig globálissá válik, var
operátorral az adott függvénytérben lévő változó pedig lokális, valamint hogy a függvények maguk is adatok, érdekes tüneményt szül. Mivel a függvény látja a vele egy érvényességi körben deklarált, de kívülről lokális változókat, és mivel a függvény maga globálisnak számít, a „kívülről” meghíva elérjük vele a „belső” lokális változónkat. Ez a closure.
function karam()
{
var fekete_bari = 'Beeeee Beeee';
function juhasz()
{
console.log('Na most jól megkörmöllek fekete bari ' + fekete_bari);
};
//juhasz(); // Na most jól megkörmöllek…
}
karam();
juhasz(); // ReferenceError Óh, jaj.
juhasz
függvény kívülről most privátnak számít. Ilyen szempontból a function fuggveny(){};
és a var fuggveny=function(){};
teljesen egyenértékű, a függvény az adott érvényességi körben privát lesz, kívülről nem elérhető. Ennek előnye is van: nem szennyezzük a globális névteret, és olyan függvényeket tudunk létrehozni, mely az adott objektumra speciális feladatokat kell csak hogy ellássanak. Ha kikommenteljük a karam
függvényben a juhasz()
hívást, akkor látjuk, milyen jól is működik.No de vissza a problémánkhoz:
function karam()
{
var fekete_bari = 'Beeeee Beeee';
juhasz = function() {
console.log('Na most jól megkörmöllek fekete bari ' + fekete_bari);
};
}
karam();
juhasz(); // Na most jól megkörmöllek…
Pár clousre példát fogunk még látni. Előtte azonban egy picit térjünk vissza a karámhoz.
Ahhoz hogy változóink létrejöjjenek, az őket tartalmazó függvényt egyszer meg kell hívni. Ez történik a
karam()
hívásnál. Ha ezt kettébontjuk, azt kapjuk, hogy a karam
egy referencia az objektumunkra (melyet mi úgy hívunk függvény), a ()
meg azt jelenti „hajtódj végre most”. Nézzük csak ezt át jobban:
function mi_a_nevem(objektum)
{
console.log(objektum.nev);
}
var objektum = {nev: 'Nekem van nevem, az hogy objektum'};
mi_a_nevem(objektum);
mi_a_nevem({nev: 'Nekem nincs nevem, Anonymus vagyok'});
function mit_csinalok(callback)
{
callback();
}
var fuggveny = function(){
console.log('Az én nevem függvény');
};
mit_csinalok(fuggveny);
mit_csinalok(function() {
console.log('Én is Anonymus vagyok, bár függvény')});
()
parancsunkat, és ha az adatunk függvény, végre is hajtódik. Ez a callback.Ezen tudásunkkal felvértezve visszatérhetünk a closure példánkhoz, és azt mondjuk: nincs karám!
(function(){
var fekete_bari = 'Beeeee Beeee';
juhasz = function() {
console.log('Na most jól megkörmöllek fekete bari ' + fekete_bari);
};
})();
juhasz(); // Na most jól megkörmöllek…
var
kulcsszó használatához áll közelebb. Pontosan ezért működik a
var fuggveny = function(beee) {
return function (){
alert(beee)
;}
;}('Beee');
Természetesen a végrehajtó zárójelpárosban megadhatjuk a paramétereket is, melyet névtelen függvényünk vár – ha vár. Nos, tehát végül a closure zárásaként írjunk egy getter–setter metóduspárost, mellyel privát változónkat érhetjük el és állíthatjuk be:
var set, get;
(function () {
var titok = 0;
get = function() {
return titok;
};
set = function(val) {
// ha val megfelel éz én általam elvárt dolgoknak…
titok = val;
};
})();
set(20);
console.log(get()); //20
console.log(typeof titok); //undefined
Nos, belepillantottunk hogyan is működik a closure, láttuk, hogy az általunk függvénynek tisztelt objektumot paraméterként át tudjuk adni, meg tudjuk hívni újra, sőt ha anonymous függvényünket kifejezésként hoztuk létre, azonnal meg is hívhatjuk. És függvényeink eszköztára még mindig nem merült ki, szemezgessünk még pár dolgot:
arguments objektum
Mint neve is sugalja, azarguments
objektum a függvény által fogadott paramétereket tárolja. (A length
tulajdonság a kapott paraméterek számát tárolja). Hasonló egy tömbhöz, de nem tartalmaz olyan metódusokat, min a slice
vagy a sort
. Mivel az arguments
tulajdonság egy objektum, persze hogy van neki is tulajdonsága: a callee
, mely referencia magára a függvényre, így akár a anonymous függvényünket rekurzívvá hívhatjuk.
(function(jo, rossz, csuf)
{
if (arguments.length > arguments.callee.length)
{
console.log('A vadnyugat szerencseszáma a három! Nem kell a ' + arguments[3] + '!');
arguments.callee(arguments[0], arguments[1], arguments[2]); //rekurzív hívás
}
else
{
console.log(arguments[0] + ' baris példákból sajnos kifogytunk.');
}
})('A Jó', 'A Rossz', 'A Csúf', 'Ráadás');
arguments
objektum nagyon hasznos tehát akkor, ha nem tudjuk előre, hogy függvényünket mennyi paraméterrel fogjuk hívni, bár véleményem szerint ilyen esetben jobb eredményt kapunk egy egyszerű objektum paraméter fogadásával, melynek tulajdonságaiként bármilyen sorrendben átadhatjuk a várt paramétereket.
function Link (config_object) {
var linkHtml = '<a ';
if (config_object.id)
linkHtml += ' id="' + config_object.id + '" ';
if (config_object.css_class)
linkHtml += ' class="' + config_object.css_class + '" ';
//href, text stb. stb.
linkHtml += '</a>';
linkObject = $(linkHtml);
if (config_object.funct)
linkObject.click(config_object.funct);
return linkObject;
}
Tegyük fel hogy meg akarjuk számolni vajon hány linket hoztunk már létre (esetleg dinamikus id létrehozás miatt, vagy ki tudja miért). Ilyen esetben is jól jöhet az
arguments.callee
.
var linkszam = 0;
function Link (config_object) {
linkszam++;
console.log(linkszam);
}
Link(); //1
Link(); //2
function Link (config_object) {
var thisFunction=arguments.callee;
if (!thisFunction.linkszam) {
thisFunction.linkszam=0;
}
thisFunction.linkszam++;
console.log(thisFunction.linkszam);
}
Link(); //1
Link(); //2
call
, apply
Előfordulhat olyan eset, hogy egy függvényt más környezetben szeretnénk meghívni, mint amelyhez eredetileg tartozik. Tehát a this
értékét át szeretnénk állítani. Erre használható a két fenti függvény, mely szintén a Function
objektum prototype-jának (tehát minden függvénynek) elérhető metódusa. Például, ha azt szeretnénk hogy az arguments
objektumunk mégis rendelkezzen az Array
konstruktor valamely metódusával, akkor egyszerűen kölcsönvehetjük:
function href () {
if (arguments.length) {
var href_joined = 'index.php?' + Array.prototype.join.call(arguments, '&');
return href_joined;
}
}
console.log(href('bari=Beee', 'kutya=Vauuu', 'tyukom=kitrakotty')); //index.php?bari=…
call
hívás első paramétere az objektum, melyre this
-ünk kell hogy mutasson. Ha ezt kihagyjuk, akkor a window
objektum lesz az alapértelmezett. A további paraméterek pedig a hívott függvény paraméterei, itt például a karakter, mellyel összefűzzük tömbünket. Az apply
annyiban különbözik a call
-tól, hogy a további paraméterek egy tömbként kell hogy átadódjanak, tehát: Array.prototype.join.apply(arguments,['&']);
.Persze nem csak kölcsönvehetjük, hanem mint már többször is utaltunk rá, módosíthatjuk is a konstruktor függvények
prototype
-ját. Ha ilyen metaprogramozási vágyaink vannak, mindig gondoljuk át, hogy valóban érdemes-e belenyúlnunk a natív függvények működésébe, és ha kódunk esetleg más programozók kezébe kerül, figyelmeztessük őket, hogy egyes dolgok máshogy működnek, mint azt várnák. Érdemes mindig vizsgálni az adott metódust, nem létezik-e még, hisz új JavaScript verziókkal, új böngészőkkel új metódusok is jelenhetnek meg.
if (!String.prototype.reverse) {
String.prototype.reverse=function() {
return Array.prototype.reverse.apply(this.split('')).join('');
}
}
if (!Array.prototype.inArray) {
Array.prototype.inArray = function(element) {
for (var j = 0, l = this.length; j < l; j++) {
if (this[j] === element) {
return true;
}
}
return false;
}
}
console.log("bari".reverse()); //irab
var barik=['fekete', 'szürke', 'fehér'];
console.log(barik.inArray('fekete')); //true
console.log(barik.inArray('zöld')); //false
Array
függvény metódusát, és alkalmazzuk azt a String
függvény ugyanolyan nevű metódusaként. Gyakorlatilag a string karaktereiből tömböt hozunk létre, arra alkalmazzuk (apply) az Array
objektum reverse
metódusát, és utána az immár megfordított tömbből új stringet illesztünk össze.A második függvénnyel az
Array
objektumunkat bővítjük, és függvényünk igazzal tér vissza, ha megtalálható a keresett elem, és hamissal ha nem.Gyakorlatilag az elmélet végére értünk. Utolsó fejezet jogán még megemlítünk pár dolgot, mely igazából az eddig átnézettek variálása, talán gyakorlatiasabb szemszögből.
Namespace – névtér
A névtér a golbális változók elkerülésének egyik módszere (másik lehet az önmagát végrehajtó függvény és benne pár closure), mellyel megakadályozhatjuk hogy egy másik társunk felülírja a mi általunk deklarált változót egy ugyanolyan nevű de később szereplő változóval.
var id = function(id) {
var elem
if (elem = document.getElementById(id))
return elem;
else
return elem;
}
var div = id('content');
var nem_div = id('bari');
console.log(div); //<div_id="content">
console.log(nem_div); //null
//sok millió sörrel…azaz…sorral később
var id = 'Nagyonfontosid'; //hoppá
//újabb sok millió sor
var div_megint = id('content'); //TypeError!;
var doom_methods= doom_methods || {};
doom_methods.id =function(id) {
var elem
if (elem = document.getElementById(id)) {
return elem;
} else {
return elem;
}
}
var div = doom_methods.id('content');
var nem_div = doom_methods.id('bari');
console.log(div); //<div_id="content">
console.log(nem_div); //null
//sok millió sörrel...azaz... sorral később
var id = 'Nagyonfontosid'; //Nincs többé hoppá!
//újabb sok millió sor
var div_megint = doom_methods.id('content'); //Minden normális
console.log(div_megint); //<div id="content">
var jQuery = window.jQuery = window.$ = function (selector, context) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
};
jQuery.fn = jQuery.prototype = {
init: function( selector, context ) { //stb. stb.
function addEvent (el, type, fn) {
if (typeof el.addEventListener === 'function') {
addEvent = function(el, type, fn) {
el.addEventListener(type, fn, false);
};
} else if (typeof el.attachEvent === 'function') {
addEvent = function(el, type, fn) {
el.attachEvent('on' + type,fn);
};
} else {
addEvent = function(el, type, fn) {
el['on' + type] = fn;
};
}
addEvent(el, type, fn);
}
var content = document.getElementById('content');
addEvent(content, 'click', function() {
alert('Működik a click!');
});
attachEvent
), akkor az event propagation, -bubbling, -capturing témákkal kapcsolatos feltételezéseink szintén helytállóak lesznek. Van eset, mikor magát a böngészőt szaglásszuk (browser sniffing), de az előbbi jobb, sajnos a böngészők terén sokszor valami bűzlik :-).Tervezési minták (design patterns)
Nos, ez lesz a legrövidebb része a cikknek, ugyanis minimum külön cikket érdemelne, és jómagam sem vagyok eléggé olvasott ahhoz, hogy egy ilyen témába belevágjak. Jó, leírhatnánk ide egy két egykét (hát ez nagyon vicces!), de nem tesszük. Akit nagyon érdekel a téma, guglizzon reá! Egy dolgot megnézünk azért, mellyel metódusokat láncolhatunk egymáshoz, és melyet kedvenc jQuery függvénykönyvtárunk is használ: a chaining.Chaining
A jQuery által is használt chaining lényege, hogy kiválasztunk egy vagy több HTML elemet az oldalon, és azokon egy vagy több módosítást hajtunk végre. Írjuk hát meg saját piciny függvénykönyvtárunkat, hisz gyakorlatilag már mindent tudunk, amire szükségünk lehet. Ez lesz a mi kis esettanulmányunk (case study :-)).Tehát ki akarunk választani egy vagy több HTML elemet, mondjuk ID alapján. Ezeket tárolnunk kell valahogy, legyen hát egy tömb. Az elemeket visszaadó függvényt védjük meg, legyen privát, kell nekünk egy closure, és egy önmagát végrehajtó függvény. Végül, mivel valamiben tárolni kell mindezt, és majd valamihez kapcsolni kell a metódusainkat, legyen a visszatérési érték objektum. Kell hát a
new
.
(function() {
function _$(elements) {
this.elements = [];
for (var j=0, l = elements.length; j<l; j++) {
var element=elements[j];
if (typeof element === 'string') {
element = document.getElementById(element);
}
this.elements.push(element);
}
}
window.$ = function() {
return new _$(arguments);
};
})();
$('id', 'megintid');
hívásnál új objektum keletkezik, és az átadott paraméterekkel meghívódik privát konstruktor függvényünk, this.elements
tulajdonsága az új objektumra mutatván tárolni fogja abban a HTML-elemek tömbjét.Akkor érjük hát el valahogy ezt a tömböt. Mivel újrahasznosítható kódról van szó, beugrik a prototype, mely minden konstruktor függvénynek sajátja, és privát függvényünknél is kiválóan fog működni. Ami pedig a láncolást illeti, nagyon egyszerű. Mivel azt szeretnénk, hogy a metódusaink az eredeti objektum módisított tömbelemein dolgozzanak, adjuk nekik mindig oda az eredeti objektumot. Magyarán, a láncban előbb levő függvény térjen vissza az objektumra való referenciával:
return this
.Nézzünk is:
(function() {
function _$(elements) {
this.elements = [];
for (var j = 0, l = elements.length; j < l; j++) {
var element = elements[j];
if (typeof element === 'string') {
element = document.getElementById(element);
}
this.elements.push(element);
}
}
_$.prototype= {
each: function(fn) {
for (var j = 0, l = this.elements.length; j < l; j++) {
fn.call(this, this.elements[j]);
}
return this;
},
addEvent: function(type, fn) {
var add = function(el) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
}
else if (window.attachEvent) {
el.attachEvent('on' + type, fn);
}
};
this.each(function(el) {
add(el);
});
return this;
},
setStyle: function(property, value) {
this.each(function(el) {
el.style[property] = value;
});
return this;
},
show: function() {
var that = this;
this.each(function(el) {
that.setStyle('display', 'block');
});
return this;
},
hide: function(){
var that = this;
this.each(function(el) {
that.setStyle('display', 'none');
});
return this;
},
setBold: function() {
var that = this;
this.each(function(el) {
that.setStyle('fontWeight', 'bold');
});
return this;
},
setbgColor: function(color) {
var that = this;
this.each(function(el) {
that.setStyle('backgroundColor', color);
});
return this;
}
};
window.$ = function() {
return new _$(arguments);
};
})();
_$.prototype
értékadás azonnal a _$
függvény után történik, hogy a closure még sikeresen magába zárja. A bezáró ()
után már sok használt nem vennénk.Az
each
függvény igazából minket segít, önmaga egy függvényt vár, egy paraméterrel, melyet szépen meghív minden tárolt HTML elemre, az fn.call(this, this.elements[j])
hívással, így a hívott függvényben szereplő this mindig az adott beburkoló objektumra mutat, melyben tömbünk elhelyezkedik.Az
addEvent()
függvényben már látunk is erre példát, mikor is először egy belső privát add()
függvényt hozunk létre, mely végez egy gyors szaglászást az elérhető böngészőmetódusokról, és annak megfelelően kapcsolja az elemhez az eseményt. Majd ezt az add
függvényt hívjuk meg az each
-en belül, tehát valahogy így fog a képzeletben kinézni: add.call(this, this.elements[j]);
és ilyesmit mond az értelmező a függvények: hívódj meg most hát a pillanatnyi burkoló objektumunkra mutatva és paraméteredként fogadd el az adott HTML tömbelemet, és ne törődj a barikkal! Az add
függvény veszi a tömbelemet, a „feljebbről” kapott type
és fn
paramétereket, szaglászik és eseményt kapcsol.A
setStyle
függvény pontosan ugyanígy működik, annyit jegyezzünk meg róla még, hogy ebben a formában a JavaScript által elfogadott CSS tulajdonságokkal dolgozik, tehát fontWeight
és nem font-weight
, vagy backgroundColor
és nem background-color
. Az utolsó négy függvényben feltűnik egy kis új elem – a
that
–, melynek átadjuk a this
-t. Igazából semmi jelentősége hogy az éppen that
, de szerintem örüljünk hogy nem bari
lett. Azért van szükség rá, mert az each
függvényben újra hívjuk a setStyle
függvényt. Mivel biztos emlékszünk rá még, hogy a this
egy speciális változó, és mint ilyen, az each
függvényen belül már nem a burkoló objektumunkra mutatna, hanem csak egy objektummal feljebb (tehát itt a show
, hide
stb. objektumokra), ami nem jó. Ezért a that
-ben tároljuk az eredeti this
referenciát, és az így hívott setStyle
függvény már a tőle elvárt módon fog működni.Ki is próbálhatjuk egy frissiben összedobott template oldalon valahogy így:
$(window).addEvent('load', function() {
$('header').addEvent('click', function() {
$('nav','promo').hide().setBold().setbgColor('red').show();
});
});
Hát ennyi volna. Nem is tudom mivel lehetne lezárni egy ilyen cikket, de remélem hogy pár új dolgot sikerült megismertetni veled kedves olvasó. Bocsánat a baris példákért, a hosszú körmondatokért, és a néha (remélem csak néha!) gyenge poénokért.
true || {} ?
Na de itt egy kérdés a cikk elejéhez kapcsolódva:
Mert én úgy tudtam hogy mindig sorban megnézi és az első true értéküt fogja visszaadni, ha meg mindegyik false akkor az utolsó false értékűt. (persze ha mindenhol || van)
"A másik dolog a JavaScript azon tulajdonsága, ha egy logikai (boolean) és egy nemlogikai kifejezés kiértékelése történik, akkor a nemlogikai eredményt fogjuk megkapni végül."
Szóval szerintem ez nem igaz mert, ha egy logikait (true) és egy nemlogikait ({}) akkor a true -t kapjuk:
üdv,
Balázs
Igen, de itt lusta
így már világos
üdv,
Balázs
És tényleg nem
Sajnos ez így nem áll meg, a felhozott példával sem. Az
&&
és||
operátorokat így lehetne kifejteni:Nagyon jó!
Köszik, üdv:
Gábor
Itt van
(Douglas Crockford is említette előadásában, amit javaslok minden Javascripttel foglalkozónak végignézni.)
Prototype.js
pl:
...
jó cikk.
Elemi típus javascriptben?
Ha vannak neki mindenféle tagváltozói meg metódusai, akkor az objektum.
Egy kis segítség, ha már a szerző ajánlgatta:
http://www.w3schools.com/jsref/jsref_obj_string.asp
http://www.w3schools.com/jsref/jsref_obj_number.asp
http://www.w3schools.com/jsref/jsref_obj_boolean.asp
Valahol azért remélem, hogy a cikk többi része nem hemzseg ennnyire a szerző félreértéseitől.
typeof
szerkesztve Közben megnéztem, és primitív adattípust írtam a cikkben.
szerkesztve a szerkesztés tényleg, írtam utólag elemit is, na mindegy mostmár, majd lesz valahogy :-)
ne a w3schools-ra alapozz ;)
http://weblabor.hu/cikkek/oojsafelszinalatt#languagedescription
"A JavaScript egy objektum alapú nyelv, melyben minden objektum, még a függvények is. Egy objektum tulajdonságokból áll. Egy tulajdonságot a neve határozza meg, és tartozik hozzá egy érték, illetve attribútumok. Az értéke lehet egy objektum, primitív érték (undefined, null, boolean, string, number) vagy metódus (függvény objektum)."
Szóval nyugodtan végig olvashatod. ;)
»for in« iteráció veszélyes
for in
ciklust, mely [...] tömböknél csak az indexelt tulajdonságokkal (tehát a tömb elemeivel) működik.Ez némileg pontatlan. Valamennyi enumerable tulajdonságot visszaadja, így az utólag az
Array
-hez adottinArray
metódust is például (ahogy pont te magad írsz ilyet a cikkben).Köszönöm
forEach
Így nézne ki egy ilyen (jQuery alapján):
natív forEach
Array Object nem mind1
Array-on mindig
Hatásvadász :-)
Új objektum létrehozása 'new' operátor nélkül
Ezt többféleképp is megtehetjük:
Egyébként gratula, nagyon ott van a cikk! Jó, hogy végre magyarul is lehet ilyesmit olvasni!
Tovább jutottál :-)
Jó lenne ha lenne egy explaining jQuery inner workings:-) vagy hasonló cikk is, én igen örömmel olvasnám.
Secrets of the JavaScript Ninja
Jól néz ki!
Manning Early Access Program
üdv Csaba
jsninja
jsninja (pont) com / Main_Page
Eleinte a google kidobta, de már nincs indexelve ;)
Asymmetrical Reading and Writing of Inherited Members
Copyright © 2008 by Ross Harmes and Dustin Diaz c. ajánlott könyvben, de tömören:
párszor már sikerült belefutni ebbe, úgyhogy érdemes megjegyezni, hogy amennyiben prototype láncolást használunk, a kompozit adattípusokból (tömb,objektum), használat előtt, mindig hozzunk létre új másolatot, egyébként a láncoláson keresztül az eredeti prototype objektumban lévő érteket fogjuk felülírni, ennek következtében vidám pár órát tölthetünk majd kódunk debug-olásával (megvolt :) ), primitív adattípusoknál nem ez történik, ezek változtatásakor csak az adott példányban lévő változó értéke fog felülíródni ill. adott néven új változó fog létrejönni a példányban és ennek adunk értéket
példa:
üdv Csaba
new Function
Igaz még sosem használtam, de a "A konstruktor függvények" résznél nekem hiányzott a new Function([arg1[, arg2[, ... argN]],] functionBody) forma említése.
pl.:
var twoNumAverage = new Function("x", "y", "return (x + y)/2")
Akarattal hagytam ki
helo
A rekurzív kód egyszerűség kedvéért 3-tól számol vissza 1-ig:
IE6-ban 3,3,2,1
viszont ha nem használom az automatikus lefutást, tehát meghívom egyszer a coundown() függvényt, akkor IE-ben is rendesen működik.
Mi lehet ennek az oka?
Rákeresgéltem
pl:
webreflection
Ám nem vagyok benne biztos hogy ez az igazság :-). IE6 mikor tűnik már el???? :-)
szerkesztve: Közben eszembe jutott, egyébként működött. Szóval a fenti akkor mégsem igaz, tehát passz :-) Majd windows alatt leszek, ígérem kipróbálom.
coundown != arguments.callee
alert(coundown == arguments.callee);
Először hamis, majd utána háromszor igaz lesz az érték, azaz az első értékadáskor nem is a coundown függvényen belül módosítasz.
Ha az első sort átírod "(coundown = function() {" -ra, akkor már jó lesz.
Hasonlóan, ahogy a if(thisFunction.i!=0)setTimeout( arguments.callee,1000); utasítással is.
Azaz az IE kicsit lassú felfogású... :)
és megy...
Új változó, de miért?
Írtad, hogy
A jQuery által is használt chaining lényege, hogy kiválasztunk egy vagy több HTML elemet az oldalon, és azokon egy vagy több módosítást hajtunk végre.
Szerintem ez így nem elég általános. A chaning lényege, hogy minden függvényhívásra visszakapj egy objectet, aminek utána meg tudod hívni egy függvényét. Jelen esetben ugyanazt az objectet adod vissza, de sok nagyon sok esetben lehet, hogy másik objectet kapsz vissza. Például ha csak tömböknél maradunk, akkor
Szerintem leírtam miért
A chaining kódrészlet nem az én találmányom, de azért megpróbálom elmagyarázni ahogy én látom:
A call függvénynek azért adom át a this hivatkozást, mert tudtommal valamit át kell adnom neki, ugyanis ha nem adok át neki semmit, akkor a window objektumra fog a this mutatni. (Tehát a call függvény alapértelmezett "mutatandó objektuma" a window.)
A that pedig azért kell, mert a második példádban az első this (a this.each) this-e arra az objektumra fog mutatni, melyben a show, each, stb függvények vannak, azaz egyel "feljebb". Viszont a második this mivel szintén csak "egyel feljebb" mutat, ezért az ő tároló objektuma a show függvény, mivel ebben az objektumban deklaráltuk anonymus függvényünket (function(el){...}). Viszont nem a show függvény a célunk, hanem az eredeti "kettővel feljebbi" objektum, mely a show függvényt is tárolja. Ezért átadjuk azt az objektumot a that változónak.
Remélem jól tudom és el is sikerült magyarázni :-)
Üdv!
Gábor
Szerintem inf3rnonak igaza
Jogos
A this viszont akkor is kell a call-ba próbáljátok ki, egyébként hibát dob (nem találja a window.elemets tömböt). Vagy csak elnézek valamit?
Üdv:
Gábor.
Nem nézed el.
Az each használatánál az eachnek paraméterként megadott függvényben a this a gyűjteményre vonatkozik, ezért felesleges a gyűjteményt külső változóból behozni.
A setStyle az összes elemre vonatkozik a gyűjteményben, mivel az each-et használja, ezért felesleges each-ben meghívni a setStyle-et.
Az összes függvény a gyűjteményt adja vissza, ezért ha egymásban használjuk őket, - mint mondjuk a setBold-nál a setStyle-t - akkor a beágyazott függvény visszatérési értékét kapásból visszaadhatjuk, nem kell feleslegesen emiatt új sorba kiírni a "return this"-t.
"}" után meg szerintem nem illik ";"-t tenni. Az egyik a blokk vége a másik a sor vége.
Szóval az összes javítással így néz ki a dolog:
"}" után meg szerintem nem
Ezt hogyan is gondoltad?
Amikor egy változót feltöltök egy értékkel, akkor ugye a sor végére teszek egy
;
-t, pl:;
a}
után ha azt a szintaktika megkívánja.Nem értek egyet
nagyon nem érted
értem én
A szabványban
ECMASCRIPT .pdf
Elolvastam
Nem lehet hogy a második eset...
szerintem meg
Jó helyen van az :-)
na mégegyszer
Tégy igazságot
:-)
Egyébként elfogadom, hogy értékadásnál a függvény után kell semicolon, de ettől függetlenül így sem fogom használni, mert inkább sort török helyette.
Probléma
OK
Pedig hasznos dolog!
Es minek irni sajatot, ha Dean Edwards es tarsai, akik aztan elegge nagy es elismert guruk mar megirtak a 'sajatjukat'!?
???
Nem hiszed vagy nem tudod?
Mily igaz!
Namespace,Class ilyesmik
Én köszönöm
igaz