ugrás a tartalomhoz

A JavaScript metaprogramozása

Ustak · 2008. Nov. 28. (P), 15.01
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.

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, írt for 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]);
}
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:

if (!$('div#popup').length) {
    $('<div id="popup">Poppp!</div>').appendTo('body').hide();
}
Á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!

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);
}
Popupunk így mindig meg fog jelenni, hisz a '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
eseté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:

var beallitasok;
var alkalmazott_beallitasok = beallitasok || {};

console.log(alkalmazott_beallitasok);
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 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;
Ugye teljes káosz!? A fent említett öt elemi típus közül négy még ok, de a 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
Ugye ez már megnyugtatóbb. Amit ebből érdemes leszűrnünk az az, hogy a 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'])}; 
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:

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.
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 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
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 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]
Mint 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:

if (p[j].constructor === Array) {
    c[j] = [];
} else {
    c[j] = {};
}
Ha már metaprogramozás, szűkítsük be ezt egy sorba, hogy sokkal elegánsabb (és olvashatatlanabb :-)) legyen:

c[j] = (p[j].constructor === Array) ? [] : {};
Komolyabbra fordítva a szót, mi lehet az a 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 a tomb.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
Megint kitűnik a hasonlóság. Mindegyik objektumunknak van 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 
Ez lényeges pont. Az 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
Mint látjuk, ha a 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 
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 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
Nahát, most szótárunk egész ügyesen beszél. Ha egy függvénynek nem adunk visszatérési értéket, 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');
A 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...
É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 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
Nahát, újabb talány. Azt tudjuk, hogy a 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...
Jobb lenne ha az '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...
Kis nyelvtani hibától eltekintve kiderül, hogy bár a 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
Tehát a már meglévő objektumunk is megkapja az új tulajdonságot, ha módosítjuk a konstruktor függvény 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...
Kemé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 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 a var 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
Szerintem a barik ismét megvilágítják a problémát. Látjuk hogy a függvényen belül a lokális 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, hogy var 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.
Ugye valamit elrontottunk. Juhászunk kívülről nem elérhető, és ennek az az oka, hogy a 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…
É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 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'}); 
Lá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:

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')});
Tehát átadjuk az adatot, vagy az adatot tartalmazó változót, utánabiggyesztjük a „hajtódj végre” () 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…
É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 var kulcsszó használatához áll közelebb. Pontosan ezért működik a

var fuggveny = function(beee) {
    return function (){
         alert(beee)
    ;}
;}('Beee');
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:

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
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:

arguments objektum

Mint neve is sugalja, az arguments 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'); 
Í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 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;
}
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 az arguments.callee.

var linkszam = 0;
function Link (config_object) {
    linkszam++;
    console.log(linkszam);
}
Link(); //1
Link(); //2
helyett

function Link (config_object) {
    var thisFunction=arguments.callee;
    if (!thisFunction.linkszam) {
        thisFunction.linkszam=0;
    }
    thisFunction.linkszam++;
    console.log(thisFunction.linkszam);
}
Link(); //1
Link(); //2
Így egy globális változóval kevesebb.

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=…
A 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
Az első függvény egy kis átvezető az előző példánkból, ugyanis itt kölcsönvesszük az 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!; 
Hát igen, ez elég zavaró. Ilyenkor segíthet egy namespace:

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">
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:

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 );
};
…illetve pár sorral lejjebb…

jQuery.fn = jQuery.prototype = {
	init: function( selector, context ) { //stb. stb.
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.

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!');
});
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. 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);
    };
})();
Így hát eddig meg is vagyunk. A $('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);
    };
})();
É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 _$.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();
    });
});
É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.
 
Ustak arcképe
Ustak
Ancsin Gábor Makón él családjával, programozóként dolgozik. Szeret a kliens oldalon tevékenykedni, és szereti, ha mindezt Linux alól teheti meg.
1

true || {} ?

solkprog · 2008. Nov. 28. (P), 20.15
Bár még nem olvastam végig (jó hosszú:) ), de majd mindenképpen megteszem (legyen egy kis időm), mert már azóta vártam rá mióta megemlítetted v.melyik topicban hogy tervezel egy JS cikket. Szóval thx a cikkért.

Na de itt egy kérdés a cikk elejéhez kapcsolódva:

var alkalmazott_beallitasok = false || {};  
console.log(alkalmazott_beallitasok); //Object
Itt azért nem kapunk Object-et mert az üres {} is igaz kiértékeléskor? (És az első igaz-t adja vissza.)
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:

var alkalmazott_beallitasok = true || {};    
console.log(alkalmazott_beallitasok); // true
És akkor a tényleges kérdés: Valóban vagy egy ilyen belső mechanizmus a JS ben hogy a nemlogikaiakat előreveszi a logikaiakkal szemben, vagy csak én néztem be? (lehet csak fáradt vagyok szakmai cikk olvasásához)

üdv,
Balázs
2

Igen, de itt lusta

Ustak · 2008. Nov. 28. (P), 20.15
A második példádnál azért kapjuk a true-t vissza, mert el sem jut az objektumig, hanem látja hogy egy "vagy" kiértékelésnél ha az első vizsgálat igaz, akkor már nem is érdekli az értelmezőt, hogy mi van tovább. (én legalábbis így tudom). Nézd ezt a példát:

var alkalmazott_beallitasok=true && 'ezjönvissza';
console.log(alkalmazott_beallitasok); //ezjönvissza
Itt már tényleg a nem boolean értéket adja vissza, mert végigjut a kifejezés kiértékelésén.
3

így már világos

solkprog · 2008. Nov. 28. (P), 20.37
...
üdv,
Balázs
53

És tényleg nem

presidento · 2010. Május. 7. (P), 21.11
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.

Sajnos ez így nem áll meg, a felhozott példával sem. Az && és || operátorokat így lehetne kifejteni:
a && b === a ? b : a;
a || b === a ? a : b;
Például

'str' && true  === true;
NaN   || false === false;

true  && 'str' === 'str';
false || NaN   === NaN;
Amúgy nagyon jó cikk, gratulálok!
54

Nagyon jó!

Ustak · 2010. Május. 8. (Szo), 14.22
Míly rég érkezett hozzászólás :-) Igazad van, nem teszteltem le száz százalékosan, és valahol olvastam az általam így félreértett állítást. Köszönöm az új infót! Ígérem, már csak kiváncsiságból is utána fogok nézni a szabványban, hogy ír -e erre valami konkrétat... Vagy esetleg ha Te is valami hasonló forrásból tudod, és belinkelnéd honnan jöttél rá, engem is érdekelne :-)
Köszik, üdv:
Gábor
55

Itt van

presidento · 2010. Május. 9. (V), 07.57
A szabvány részletesen leírja.

(Douglas Crockford is említette előadásában, amit javaslok minden Javascripttel foglalkozónak végignézni.)
56

Prototype.js

inf3rno · 2010. Május. 12. (Sze), 05.18
Prototype.js régebbi verziója elég gyakran használta ki ezt a dolgot.

pl:

Array.prototype.each=function (method,context)
{
	for (var i=0,l=this.length; i<l; ++i)
	{
		method.call(context || this, this[i]);
	}
};
Máshol nem nagyon láttam, de az utóbbi 3 évben csak írtam javascript kódot, és nem olvastam. (write only :D)
4

...

demo · 2008. Nov. 28. (P), 21.12
elsőre nem is tudtam végig elolvasni, annyi új infóhoz jutottam.
jó cikk.
5

Elemi típus javascriptben?

amonrpg · 2008. Nov. 28. (P), 22.28
Én ott hagytam abba az olvasást, hogy elemi típus JS-ben.
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.
6

typeof

Ustak · 2008. Nov. 28. (P), 23.00
A szerző itt kér elnézést. Arra próbált rávilágítani a szerző, hogy a typeof operátor által megkülönböztetett típusok szerint miként diszkriminál a javascript. Lehet, hogy a primitív adattípus jobb kifejezés lett volna, valóban, az angol irodalmakban azt használják.
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 :-)
13

ne a w3schools-ra alapozz ;)

Hodicska Gergely · 2008. Nov. 29. (Szo), 17.38
Ha vannak neki mindenféle tagváltozói meg metódusai, akkor az objektum

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. ;)
7

»for in« iteráció veszélyes

Török Gábor · 2008. Nov. 29. (Szo), 08.14
Még mielőtt lemetaprogramozzuk ezt a problémát, csempésztem a fenti példába egy 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 adott inArray metódust is például (ahogy pont te magad írsz ilyet a cikkben).
8

Köszönöm

Ustak · 2008. Nov. 29. (Szo), 08.59
ezt tényleg jó példa!
9

forEach

Carl · 2008. Nov. 29. (Szo), 10.34
Ezért is érdemes, hogy külön is legyen erre egy függvény. Így könnyen el lehet kerülni pl az egyszerű ciklus és az event handler találkozásánál fellépő problémát, plusz a ciklusoknál javascriptben nincsenek blokk-szintű változók, így az indexhez használt változó a cikluson kívül is látható lesz (pláne ha elfelejtjük a 'var'-t használni)!

Így nézne ki egy ilyen (jQuery alapján):

function forEach( object, callback ) {
	if ( object.length == undefined ) {
		for ( var name in object )
			if ( callback.call( object[ name ], name, object[ name ] ) === false )
				break;
	} else
		for ( var i = 0, length = object.length, value = object[0]; 
			i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}

	return object;
}
28

natív forEach

tgr · 2008. Dec. 5. (P), 08.08
JS 1.6-ban már van natív forEach és for each...in.
20

Array Object nem mind1

postm · 2008. Dec. 1. (H), 16.05
For ciklusoknál és alapvetően a tárolásnál sokszor találkozni azzal, hogy mivel az Array úgyis object ezért legyen csak object. Ez nagyon nem igaz. Az Array sokkal több, mint egy object. Kihasználja, hogy az object elemei nullától növekvő számokkal indexáltak és ebből adódóan rengeteg feature-t ad.

Array-on mindig
for ( var i = 0; i < myArray.length; i++ )
- lépkedek...
22

Hatásvadász :-)

Ustak · 2008. Dec. 1. (H), 20.02
Igazad van, csak azért domborítottam ki ezt a dolgot ennyire, hogy lássuk, ez a tömb másmilyen tömb mint mondjuk (de régen volt már:-)) az object pascalban.
10

Új objektum létrehozása 'new' operátor nélkül

Carl · 2008. Nov. 29. (Szo), 10.45
Jobb lenne ha az 'Explain' string helyett magát a referenciát tudnánk vizsgálni...

Ezt többféleképp is megtehetjük:

// normál hívás
if(! ( this instanceof arguments.callee ) )  
if(! ( this.constructor == arguments.callee ) )
if( this==window ) //ez nem mindenhol működik!
if ('Explain' in this) //az új objektumnak is lehet egy 'Explain' tulajdonsága!

//Ez után meg ugye általában meghívjuk a függvényt újból a new operátorral, 
// így azt nem is szükséges használni, ráadásul így nincs szükségünk 
// még egy változóra, mint pl a _$ az utolsó példában
return new arguments.callee()
És ha már a jQueryt említetted, ott egészen érdekesen van ez megoldva, bár régen ott is valahogy így csinálták, de mostmár így néz ki:

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. 
}

//és jóval lejjebb ez után jön még egy sor:
jQuery.prototype.init.prototype = jQuery.prototype;
.
Egyébként gratula, nagyon ott van a cikk! Jó, hogy végre magyarul is lehet ilyesmit olvasni!
11

Tovább jutottál :-)

Ustak · 2008. Nov. 29. (Szo), 15.07
Bevallom, ennyire tüzetesen nem néztem át a jQueryt, de ami késik nem múlik :-) Mind a két hozzászólás nagyon helytálló, köszi érte!
Jó lenne ha lenne egy explaining jQuery inner workings:-) vagy hasonló cikk is, én igen örömmel olvasnám.
12

Secrets of the JavaScript Ninja

Hodicska Gergely · 2008. Nov. 29. (Szo), 17.24
http://manning.com/resig/
14

Jól néz ki!

Ustak · 2008. Nov. 29. (Szo), 20.07
2009 Április :-)
15

Manning Early Access Program

toxin · 2008. Nov. 30. (V), 09.55
-on keresztül azért már ellehet kezdeni olvasgatni, más kérdés, hogy pont ott fejeződik be ahol kezdene izgalmas lenni :)

üdv Csaba
18

jsninja

Carl · 2008. Nov. 30. (V), 19.04
Ezt csak így halkan mondom, nem kell nagyon terjeszteni, mert a végén még leveszi :)
jsninja (pont) com / Main_Page
Eleinte a google kidobta, de már nincs indexelve ;)
16

Asymmetrical Reading and Writing of Inherited Members

toxin · 2008. Nov. 30. (V), 12.28
hozzászólás címére keressetek rá, a Pro JavaScript™ Design Patterns
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:
var FooA = function(){};
FooA.prototype.fooComposite  = [];
FooA.prototype.fooPrimitive  = "";

var FooB = function () {};
FooB.prototype = new FooA;
FooB.prototype.constructor = FooB;

var FooC = function () {};
FooC.prototype = new FooB;
FooC.prototype.constructor = FooC;

var 
	fooA = new FooA,
	fooB = new FooB,
	fooC = new FooC;

fooA.fooPrimitive = "fooAbar";
fooB.fooPrimitive = "fooBbar";
fooC.fooPrimitive = "fooCbar";
console.log(fooA.fooPrimitive); //fooAbar
console.log(fooB.fooPrimitive); //fooBbar	
console.log(fooC.fooPrimitive); //fooCbar

fooA.fooComposite.push(["fooAbar"]);
fooB.fooComposite.push(["fooBbar"]);
fooC.fooComposite.push(["fooCbar"]);
console.log(fooA.fooComposite); //fooAbar,fooBbar,fooCbar
console.log(fooB.fooComposite); //fooAbar,fooBbar,fooCbar
console.log(fooC.fooComposite); //fooAbar,fooBbar,fooCbar
utolsó pár sorban megfigyelhető a fentebb leírt jelenség a gyakorlatban is, push metódussal direktbe írtuk FooA.prototype objektumába, ezért a változtatások az összes példányban megjelentek, legegyszerűbben úgy kerülhető el a probléma, hogy a kompozit adattípusokat a konstruktor függvényekben vezetjük be tehát:
var FooA = function(){ this.fooComposite  = []; };
FooA.prototype.fooPrimitive  = "";

var FooB = function () {this.fooComposite  = [];};
FooB.prototype = new FooA;
FooB.prototype.constructor = FooB;

var FooC = function () {this.fooComposite  = [];};
FooC.prototype = new FooB;
FooC.prototype.constructor = FooC;

var 
	fooA = new FooA,
	fooB = new FooB,
	fooC = new FooC;

fooA.fooPrimitive = "fooAbar";
fooB.fooPrimitive = "fooBbar";
fooC.fooPrimitive = "fooCbar";
console.log(fooA.fooPrimitive); //fooAbar
console.log(fooB.fooPrimitive); //fooBbar	
console.log(fooC.fooPrimitive); //fooCbar

fooA.fooComposite.push(["fooAbar"]);
fooB.fooComposite.push(["fooBbar"]);
fooC.fooComposite.push(["fooCbar"]);
console.log(fooA.fooComposite); //fooAbar
console.log(fooB.fooComposite); //fooBbar
console.log(fooC.fooComposite); //fooCbar
szebb megoldás a konstruktorok láncolása
var FooA = function(){ this.fooComposite  = []; };
FooA.prototype.fooPrimitive  = "";

var FooB = function () {
	FooB.superclass.constructor.call(this);
};
FooB.prototype = new FooA;
FooB.prototype.constructor = FooB;
FooB.superclass = FooA.prototype;

var FooC = function () {FooC.superclass.constructor.call(this);};
FooC.prototype = new FooB;
FooC.prototype.constructor = FooC;
FooC.superclass = FooB.prototype;

var 
	fooA = new FooA,
	fooB = new FooB,
	fooC = new FooC;

fooA.fooPrimitive = "fooAbar";
fooB.fooPrimitive = "fooBbar";
fooC.fooPrimitive = "fooCbar";
console.log(fooA.fooPrimitive); //fooAbar
console.log(fooB.fooPrimitive); //fooBbar	
console.log(fooC.fooPrimitive); //fooCbar

fooA.fooComposite.push(["fooAbar"]);
fooB.fooComposite.push(["fooBbar"]);
fooC.fooComposite.push(["fooCbar"]);
console.log(fooA.fooComposite); //fooAbar
console.log(fooB.fooComposite); //fooBbar
console.log(fooC.fooComposite); //fooCbar
amire írhatunk függvényt is
function extend(subClass, superClass) {
	var F = function() {};
		F.prototype = superClass.prototype;
		subClass.prototype = new F();
		subClass.prototype.constructor = subClass;
		subClass.superclass = superClass.prototype;
		if(superClass.prototype.constructor == Object.prototype.constructor) {
		superClass.prototype.constructor = superClass;
	}
}

var FooA = function(){ this.fooComposite  = []; };
FooA.prototype.fooPrimitive  = "";

var FooB = function () {FooB.superclass.constructor.call(this);};
extend(FooB,FooA);

var FooC = function () {FooC.superclass.constructor.call(this);};
extend(FooC,FooB);

var 
	fooA = new FooA,
	fooB = new FooB,
	fooC = new FooC;

fooA.fooPrimitive = "fooAbar";
fooB.fooPrimitive = "fooBbar";
fooC.fooPrimitive = "fooCbar";
console.log(fooA.fooPrimitive); //fooAbar
console.log(fooB.fooPrimitive); //fooBbar	
console.log(fooC.fooPrimitive); //fooCbar

fooA.fooComposite.push(["fooAbar"]);
fooB.fooComposite.push(["fooBbar"]);
fooC.fooComposite.push(["fooCbar"]);
console.log(fooA.fooComposite); //fooAbar
console.log(fooB.fooComposite); //fooBbar
console.log(fooC.fooComposite); //fooCbar
extend függvény részletesen kitárgyalásra kerül a fent említett könyvben, prototype kódbázis alatt a probléma a következőképpen fest by Szabi kolléga :)

üdv Csaba
17

new Function

T.G · 2008. Nov. 30. (V), 14.21
Érdekes cikk, bár szerintem hosszú, egyszerre nehezen emészthető, így én is anélkül írok, hogy teljesen átrágtam volna rajta magamat... :)

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")
19

Akarattal hagytam ki

Ustak · 2008. Nov. 30. (V), 20.02
Én sem használtam soha, de az eval() függvénnyel egyenértékűen negatívnak mutatják be mindenhol ahol találkoztam vele, ezért kihagytam. Igazad van, az "egészhez" hozzátartozik, tehát jó hogy itt megemlítetted :-).
21

helo

demo · 2008. Dec. 1. (H), 19.17
Arguments objektum bekezdésnél láttam ezt a függvénylefutási módszert, hogy zárójelbe írjuk a fgv-t. Írtam is egy példát de IE6-ban érdekes jelenséget tapasztaltam.
A rekurzív kód egyszerűség kedvéért 3-tól számol vissza 1-ig:

(function coundown() {

	var thisFunction = arguments.callee;
	if(!thisFunction.i)thisFunction.i=3;

	alert(thisFunction.i);

	thisFunction.i--;
	if(thisFunction.i!=0)setTimeout(coundown,1000);

})();
FF-ban 3,2,1
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?
23

Rákeresgéltem

Ustak · 2008. Dec. 1. (H), 20.33
mert még én sem találkoztam ilyennel, és ha jól látom a dolgokat a setTimeout a bugos.
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.
24

coundown != arguments.callee

T.G · 2008. Dec. 2. (K), 14.21
Tedd a következő utasítást a var thisFunction = arguments.callee; után:
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ú... :)
25

és megy...

demo · 2008. Dec. 2. (K), 19.26
tökjó, kösz!
26

Új változó, de miért?

inf3rno · 2008. Dec. 3. (Sze), 07.53
Üdv, kiemeltem ezt a két részt a kódból:

         each: function(fn) {  
             for (var j = 0, l = this.elements.length; j < l; j++) {  
                 fn.call(this, this.elements[j]);  
             }  
         return this;  
         }, 


         show: function() {  
             var that = this;  
             this.each(function(el) {  
                 that.setStyle('display', 'block');  
             });  
             return this;  
         },  
Itt világosan látszik, hogy a call-ban a this-t adod meg az fn függvényen belüli this-nek is. Ezek után nem értem, hogy például a show függvényben mit keres ott az a 'that' változó, miért kell erre dupla elérést biztosítani? A másik, hogy az each is thissel tér vissza, tehát a return után közvetlenül beírható, de ez már csak részletkérdés.

         show: function() {  
             return this.each(function(el) {  
                 this.setStyle('display', 'block');  
             }); 
         },  
szerk:
Í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

Array.prototype.select=function (fn)
{
	var that=[];
	for (var i=0; i<this.length; i++)
	{
		if (fn.call(that,this[i]))
		{
			that.push(this[i]);
		}
	}
	return that;
}
ennél már egy másik tömböt adsz vissza, nem ugyanazt, mert mondjuk ugyanarra szükséged van még máshol. És az ugyanúgy chainelésre használható.
27

Szerintem leírtam miért

Ustak · 2008. Dec. 4. (Cs), 18.15
Először is a megjegyzésed második részében teljesen igazad van, a chaining általánosabb dolgokra is használható, mint a html -elemek módosítása. Ez tényleg így van köszi a helyreigazításé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
29

Szerintem inf3rnonak igaza

Carl · 2008. Dec. 5. (P), 21.39
Szerintem inf3rnonak igaza van, ugyanis a call miatt a this az első paraméterre fog hivatkozni, függetlenül attól, hogy a függvény hol van. Egyébként az each fölösleges is, ennyi is elég volna:

         show: function() {  
             return this.setStyle('display', 'block');  
         },
emiatt:

         setStyle: function(property, value) {  
             this.each(function(el) {  
                 el.style[property] = value;  
             });  
         },  
30

Jogos

Ustak · 2008. Dec. 6. (Szo), 16.25
Igazatok van, tényleg lehet íjmódon egyszerűsíteni a kódot, az az igazság hogy e felett kicsi átfutottam, már be akartam fejezni a cikket :-). Köszi a helyreigazításért!
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.
32

Nem nézed el.

inf3rno · 2008. Dec. 14. (V), 22.05
Az each deklarálásánál valóban kell a call, és ahhoz a this, különben a paraméternek megadott "fn" függvény nem tudja, hogy mire hívták, és a window-ot fogja venni a this-nek.

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:

(function ()
{  
	function _$(elements)
	{  
		this.elements = [];  
		for (var j = 0, l = elements.length; j < l; j++)
		{  
			var element = elements[j];  
			this.elements.push(typeof(element)=='string'
				?document.getElementById(element)
				: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)
		{  
			return this.each(function(el)
			{  
				if (window.addEventListener)
				{  
					el.addEventListener(type, fn, false);  
				}  
				else if (window.attachEvent)
				{   
					el.attachEvent('on' + type, fn);  
				}  
			});
		},  
		setStyle: function(property, value)
		{  
			return this.each(function(el)
			{  
				el.style[property] = value;  
			});
		},  
		show: function()
		{  
			return this.setStyle('display', 'block');  
		},  
		hide: function()
		{  
			return this.setStyle('display', 'none');   
		},  
		setBold: function()
		{  
			return this.setStyle('fontWeight', 'bold');  
		},  
		setbgColor: function(color)
		{  
			return this.setStyle('backgroundColor', color);  
		}  
	}
	window.$ = function()
	{
		return new _$(arguments);  
	}
})();  
33

"}" után meg szerintem nem

Poetro · 2008. Dec. 15. (H), 14.27
"}" után meg szerintem nem illik ";"-t tenni. Az egyik a blokk vége a másik a sor vége.

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:
var x = 1234;
Szerinted ez mennyiben tér el a következőktől:
var x = {"x": 123, "y": 456};
var x = function (y) {
  z;
};
Azaz KELL a ; a } után ha azt a szintaktika megkívánja.
35

Nem értek egyet

inf3rno · 2008. Dec. 27. (Szo), 08.40
Ebben nem értünk egyet, ennyi erővel az összes object és function után rakhatnál ;-et. Melyik nyelvben látsz ilyet?
37

nagyon nem érted

gex · 2008. Dec. 27. (Szo), 13.54

function a() {
    return 0;
}
// nem ugyanaz mint a
var b = function() {
    return 1;
};
// vagy ha így jobban tetszik
var c = function(){return 1;};
elég jól látszik hogy a c értéke function(){return 1;} és az értékadás miatt kell mégy egy pontosvessző. egyáltalán nem hasonlítható össze egy függvénydeklarációval, mivel ott nincs értékadás.
39

értem én

inf3rno · 2008. Dec. 27. (Szo), 22.13
Értem én, de nem értek egyet, a kettő gyakorlatilag egyenértékű. Feleslegesen meg nem teszem ki, mert egyrészt ronda, másrészt meg ronda és nem tetszik.
40

A szabványban

Ustak · 2008. Dec. 27. (Szo), 22.51
2 oldalon keresztül foglalkoznak a témával, a 7.9. fejezetben, esetleg akit érdekel :-)
ECMASCRIPT .pdf
41

Elolvastam

inf3rno · 2008. Dec. 27. (Szo), 23.30
Elolvastam a részt, de sajnos erről nem esett szó benne, viszont elhiszem, hogy értékadásnál kell a semicolon, mert kipróbáltam :-)

function x(){}b=3
működik, míg

x=function (){}b=3
nem.
42

Nem lehet hogy a második eset...

Ustak · 2008. Dec. 28. (V), 20.08
azért dob hibát, mert nem az "automatic semicolon insertion" bukik el (hisz azt írja, minden newline karakternél és } karakternél be kellene illesztenie a ;-t -hihi, jó magyaros), hanem mert változó deklarációt vár, ami ugye így is nézhet ki:

  var x=function(){},b=3
és így működik is. Na nem mintha nem lenne mindegy, gondolom úgyis odarakjuk azt a ;-t legalább is a fejezet végén azt írja:
it is a good idea for the programmer to provide an explicit semicolon at the end of the preceding statement rather than to rely on automatic semicolon insertion.
43

szerintem meg

gex · 2008. Dec. 28. (V), 20.57
az a b=3 a kapcsos zárójeleken belül akart íródni.
44

Jó helyen van az :-)

Ustak · 2008. Dec. 28. (V), 21.17
Most épp erről folyik a társalgás, hogy kell-e nem kell-e szép-e nem szép-e és miért kell ide amoda meg nem a ";" :-)
45

na mégegyszer

gex · 2008. Dec. 29. (H), 15.43
function x(){}b=3
működik, míg
x=function (){}b=3
nem.

az a b=3 a kapcsos zárójeleken belül akart íródni
46

Tégy igazságot

Ustak · 2008. Dec. 29. (H), 16.11
kedves inf3rno, hova szántad azt a b=3 -at? :-)
47

:-)

inf3rno · 2009. Jan. 13. (K), 20.45
Természetesen a kapcsos zárójelen kívülre.

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.
48

Probléma

Poetro · 2009. Jan. 13. (K), 23.52
Ezzel csak akkor lesz probléma, ha egy nem túl inteligens minify-oloval tömöríted össze a JSt, mert akkor előjön az előbb elmített hiba.
49

OK

inf3rno · 2009. Jan. 18. (V), 17.21
Tisztában vagyok vele. Nem hiszem, hogy tömöríteni fogom a js fájljaimat, vagy ha mégis, akkor írok rá saját progit.
50

Pedig hasznos dolog!

Adam · 2009. Jan. 18. (V), 20.52
Ha nem minify-olod, vagy pack-eled, akkor joval nagyobb adathalmazt kell letoltenie/feldolgoznia a latogatod bongeszojenek, ami bizony ido, es a felhasznalod fog varni...

Es minek irni sajatot, ha Dean Edwards es tarsai, akik aztan elegge nagy es elismert guruk mar megirtak a 'sajatjukat'!?
51

???

inf3rno · 2009. Jan. 30. (P), 19.58
Most erre mit mondjak, nem hiszem, hogy a nagy "js guruk" sokkal jobbat írnának, mint én.
52

Nem hiszed vagy nem tudod?

Fraki · 2009. Jan. 30. (P), 23.29
Nem hiszed vagy nem tudod?
34

Mily igaz!

Ustak · 2008. Dec. 15. (H), 15.13
Valóban, ez így sokkal életszerűbb, köszi szépen! Eszembe juthatott volna, hogy a legtöbb plugin jqueryben hasonlóan kezdődik (return this.each...) :-)
36

Namespace,Class ilyesmik

inf3rno · 2008. Dec. 27. (Szo), 13.12
Írtam egy kis kiegészítést a cikkhez:

/*
function Namespace(method)
	új névteret hoz létre
		a this a megadott függvényen(method) belül a névtérre vonatkozik
		a visszatérő érték a névtér
*/
function Namespace(method)
{
	var nameSpace={}
	method.call(nameSpace);
	return nameSpace;
}


/*
function Object.Extends (destination,source)
	kibővít egy objectet(destination) egy másik object(source) tulajdonságaival
		(kivétel, ha a source tulajdonságnak értéke: null, ilyenkor az adott tulajdonság nem kerül másolásra)
*/
Object.Extends=function (destination,source)
{
	for (var property in source)
	{
		if (source[property]!==null)
		{
			destination[property]=source[property];
		}
	}
	return destination;
}



/*
Namespace Class
	
	function Class(source,existent)
		új osztályt hoz létre a megadott tulajdonságok(source) alapján
			a sourceban a statikus tulajdonságok mindig nagy betűvel kezdődnek, a nem statikusak pedig kicsivel
		ha az existentet is megadjuk, akkor már létező függvényt választunk az osztály alapjául 
			(mint például Array,Function,RegExp stb..), az existent értékének nyilván ennek kell lennie
		a megadott tulajdonságok közül a construct függvény lesz a konstruktor, tehát ez hívódik meg minden példányosításnál
		visszatérő értéke az új osztály(class)
		
	function class.Extends(parent,nonstatic)
		a szülő osztály(parent) tulajdonságait másolja a gyerekébe(class)
			a szülő a class.parent ill this.parent-tel érhető el a classből
		a nonstatic boolean bekapcsolásánál(true) a statikus tulajdonságok nem másolódnak
			
	function class.Implements(source)
		a megadott interfész(source) tulajdonságait másolja le
			az interfész egy object, szóval megvalósítások vannak benne 
			a statikus és nem statikus tulajdonságok ugyanúgy nagy és kis kezdőbetű alapján különböztethetőek meg benne
*/
Namespace(function ()
{
	var staticPattern=/^([A-Z].*)$/;
	var classInterface=
	{
		Extends: function (parent,nonstatic)
		{
			if (this.parent)
			{
				return this;
			}
			//nem statikus tulajdonságok
			var destination=this.prototype;
			var source=parent.prototype;
			for (var property in source)
			{
				if (!(property in destination) || destination[property]===null)
				{
					destination[property]=source[property];
				}
			}
			destination.parent=source;
			//statikus tulajdonságok
			if (!nonstatic)
			{
				for (var staticProperty in parent)
				{
					if (!(staticProperty in this) || this[staticProperty]===null)
					{
						this[staticProperty]=parent[staticProperty];
					}
				}
			}
			this.parent=parent;
			return this;
		},
		Implements: function (source)
		{
			if (source)
			{
				var destination=this.prototype;
				for (var property in source)
				{
					if (staticPattern.test(property))
					{
						var staticProperty=property.match(staticPattern)[1];
						this[staticProperty]=source[property];
						continue;
					}
					destination[property]=source[property];
				}
			}
			return this;
		}
	}
	
	Class=function (source,existent)
	{
		return Object.Extends(existent || function ()
		{
			if (typeof(this.construct)=="function")
			{
				this.construct.apply(this,arguments);
			}
		},classInterface).Implements(source);
	}
});

A működése a már jól ismert példánkon:

var DOM=Namespace(function ()
{
	var Collection=Class(
	{
		construct: function (elements)
		{
			this.elements=[];
			for (var j=0,l=elements.length; j<l; j++)
			{
				var element=elements[j];
				this.elements.push(typeof(element)=='string'
					?document.getElementById(element)
					:element);
			}
		},
		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)
		{  
			return this.each(function(el)
			{  
				if (window.addEventListener)
				{  
					el.addEventListener(type, fn, false);  
				}  
				else if (window.attachEvent)
				{   
					el.attachEvent('on' + type, fn);  
				}  
			});
		},  
		setStyle: function(property, value)
		{  
			return this.each(function(el)
			{  
				el.style[property] = value;  
			});
		},  
		show: function()
		{  
			return this.setStyle('display', 'block');  
		},  
		hide: function()
		{  
			return this.setStyle('display', 'none');   
		},  
		setBold: function()
		{  
			return this.setStyle('fontWeight', 'bold');  
		},  
		setbgColor: function(color)
		{  
			return this.setStyle('backgroundColor', color);  
		}  
	});

	this.getCollection=function ()
	{
		return new Collection(arguments);  
	}
	
}); 

var $=DOM.getCollection;
/*
	csak azért így írtam, hogy látszódjon hogy lehet elérni a névtér public tulajdonságait kívülről
*/
Ez a namespace/package egyébként nagyon tetszik, nem is gondoltam rá eddig, hogy használom, szóval végső soron megérte elolvasnom a cikket, köszi.
38

Én köszönöm

Ustak · 2008. Dec. 27. (Szo), 20.02
hogy írtál példát, és hogy elolvastad :-)
31

igaz

inf3rno · 2008. Dec. 14. (V), 21.36
Azon meg én siklottam át :-) valóban a setStyle-ban már szerepel az each egyszer, szóval felesleges még egyszer meghívni.