ugrás a tartalomhoz

Objektumorientált JavaScript programozás a felszín alatt

Hodicska Gergely · 2006. Jún. 13. (K), 14.00
Objektumorientált JavaScript programozás a felszín alatt
Jelen írásomban egy érdekes aspektusból szeretném bemutatni a JavaScriptben történő objektum orientált programozás lehetőségeit, de főként a miértjeit. Ennek érdekében górcső alá vesszük a JavaScript belső működését is ebből a szempontból: bátran merem állítani, hogy szemléletmód kialakító cikkről lesz szó. Ezen belső mechanizmusok az ECMAScript specifikáció által meghatározottak, ezért az itt leírtak nemcsak JavaScriptre vontakoznak, hanem bármilyen erre a szabványra épülő nyelvre érvényesek lesznek, így például ActionScripttel foglalkozók számára is hasznos lehet.

Ezen cikk megírását egy – Balogh Tibor által indított – wl-levlista szál ihlette; nagyon sok anyagot találhatunk ebben a témában, de egyikük sem taglal egy nagyon fontos témakört: milyen mechanizmusok is húzódnak meg a háttérben. Persze ezen tudás nélkül is nagyszerűen elboldogulhatunk, de ha ismerjük őket, könnyebben fogunk tudni nem szokványos problémákat is megoldani, majd a cikkben is fogunk találkozni olyan trükkös felhasználási javaslattal, melyről általában nem esik szó, pedig az elkövetkezendők fényében egyszerű, logikus megoldásnak tűnnek majd. Mint ahogy bevezetőben is említettem ezeket a működési szabályokat az ECMAScript specifikáció tartalmazza, melynek kidolgozását ugyanaz a szervezet (Ecma International) végezte, mint a C# vagy az Eiffel standardizálását.

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). Az attribútumok belső célokat szolgálnak, közvetlenül nem érhetjük el, nem állíthatjuk őket; négyféle van belőlük:
  • ReadOnly: a tulajdonság csak olvasható, bármilyen írási kísérlet hatás nélkül marad.
  • DontEnum: a tulajdonság nem felsorolható, nem jelenik meg például egy for in ciklusban.
  • DontDelete: a tulajdonság nem törölhető, bármilyen törlési kísérlet hatás nélkül marad.
  • Internal: belső célra használt tulajdonság.

Objektumok létrehozása

Más nyelvektől eltérően a JavaScriptben nincsenek kifejezetten osztályok. Objektumokat egyszerű függvények segítségével hozhatunk létre, melyek a new operátorral együtt használva konstruktorként működnek, a bennük lefutó kód hozza létre az objektum tulajdonságait (illetve azok egy részét). A függvényen belül ilyenkor a this változó testesíti meg a létrehozandó objektumot, mint azt az alábbi példa mutatja:

function classFoo() {
    this.propFoo = 5;
}
var objFoo = new classFoo();
alert(objFoo.propFoo); // 5

A prototype tulajdonság

Minden egyes függvény objektum rendelkezik egy kitüntetett szereppel bíró prototype nevű tulajdonsággal, melyek maguk is objektumok és amelyek alapértelmezett értéke a beépített Object objektum. Amikor egy függvényt konstruktorként használunk, és segítségével létrehozunk egy objektumot, akkor keletkezik bennük egy belső referencia az őket létrehozó konstruktor prototype tulajdonságára, ami viszont szintén egy objektum, ezért maga is rendelkezik egy belső referenciával az őt létrehozó konstruktor prototype tulajdonságára, és így tovább: végeredményben minden egyes objektumhoz tartozni fog egy prototípus lánc, melynek végén az Object található. Mindjárt kiderül, hogy miért is fontos számunkra a prototípus lánc.

Tulajdonságok olvasása, írása

Az egyik fontos tárgyalásra kerülő mechanizmus a tulajdonság hivatkozások feloldása, mi is történik akkor, amikor használjuk vagy beállítjuk azok értékét. Az írás az egyszerűbb eset: amennyiben az objektumban már létezik az adott nevű tulajdonság (és a ReadOnly attribútuma nem true), akkor annak értéke felülíródik, amennyiben nem létezik, akkor létrejön egy új a megadott névvel és értékkel. A kiolvasás már egy kicsit bonyolultabb folyamat. Amennyiben az objektum rendelkezik az adott tulajdonsággal, akkor annak értékét kapjuk vissza. Ha nem, akkor megvizsgálásra kerül a prototípus láncban szereplő következő elem, és mindez folytatódik mindaddig, amíg valamelyik objektumban nem fogja tartalmazni a keresett tulajdonságot, vagy pedig a lánc végére nem érünk. Ez utóbbi esetben a hivatkozás értéke undefined lesz.

Prototípus alapú öröklődés

Ez alapján talán már ki is találhatjuk, hogy hogyan is zajlik a prototípus alapú öröklődés a JavaScript esetében. Amennyiben egy konstruktor (függvény objektum) prototípusában létrehozunk egy tulajdonságot, akkor az látszani fog minden egyes ezen konstruktor által létrehozott objektumban is. Ha a prototípusnak egy teljes objektumot adunk értékül, akkor az azt jelenti, hogy annak összes tulajdonsága meg fog jelenni a gyerek objektumban. Sasszemű olvasóinknak ezen kívül egyből feltűnhet az is, hogy a fentiek értelmében egy konstruktorban definiált tulajdonság "elfedi" az ugyanolyan néven a prototípusban már esetlegesen létező tulajdonságot.

function classFooParent() {
    this.propFoo = 5;
    this.propBar = 5;
}

function classFooChild() {
    this.propBar = 4;
}

classFooChild.prototype = new classFooParent;

var objFoo = new classFooChild();
alert(objFoo.propFoo); // 5
alert(objFoo.propBar); // 4
A prototípus az objektum létrehozása után is megváltoztatható, és mivel referenciaként tárolódik a rajta végzett módosítások egyből látszani fognak az objektumban is. Az írási szabály értelmében viszont az objektumon végzett változtatások nem befolyásolják a prototípust.

var objFoo = new classFooChild();
classFooChild.prototype.newPropFoo = 5;
objFoo.newPropBar = 5;
alert(objFoo.newPropFoo); // 5
alert(classFooChild.prototype.newPropBar); // undefined
Szépen haladunk, de még mielőtt rátérnénk a komolyabb témákra meg kell még ismernünk pár folyamatot, melyek egy JavaScript program végrehajtásakor lezajlanak. Némelyikük talán nem fog olyan fontosnak tűnni, de majd meglátjuk, hogy ezek tudatában milyen jó kis trükköket lehet alkalmazni, sőt akár némi kis memória foglalástól/szivárgástól is megkímélhetjük magunkat.

Futási környezet (execution context)

JavaScriptben minden kód egy úgynevezett futási környezetben hajtódik végre. A program futása a globális futási környezetben indul, majd amikor meghívunk egy függvényt, akkor egy új jön létre, és egészen addig ez lesz érvényben, amíg egy újabb függvény hívásra kerül sor, vagy visszatérünk az adott függvényből. Ez utóbbi esetben az futási környezet megszűnik. Egy futási környezet létrejöttekor több fontos esemény történik, ezeket viszont befolyásolja a kód jellege is, melyet ebből a szempontból három csoportba sorolhatunk:
  • globális kód: ebbe a csoportba tartozik minden nem függvény belsejében, vagy eval()-lal végrehajtott kód.
  • eval() kód: eval() parancs által végrehajtott kód. Ebben az esetben az eval() parancsot végrehajtó futási környezetre hívó futási környezet néven fogunk hivatkozni.
  • függvény kód: ide tartozik az összes függvény belsejében végrehajtott kód, valamint a Function konstruktor számára átadott függvénytörzs paraméter által tartalmazott kód is (JavaScript esetén lehetséges függvényt létrehozni a Function objektum konstruktorként történő használata esetén).

Globális objektum

A globális objektum a globális futási környezetbe való belépés előtt keletkezik, és tuljdonságokként tartalmazza az összes beépített objektumot (String, Number, Date stb.), függvényeket (parseInt() stb.) valamint a befogadó környezet (böngésző stb.) által nyújtott objektumokat (window stb.).

[[scope]]

Minden egyes függvény objektum rendelkezik egy ilyen nevű belső tulajdonsággal, mely az objektum létrehozásakor kap értéket attól függően, hogy az milyen környezetben jön létre. Ez igazából egy akár több objektumból álló lista, hasonlóan a prototípus lánchoz. Function konstruktorral létrehozott függvény esetén ebben a listában csak a globális objektum van, míg a function kulcsszóval definiált függvények esetében az őket létrehozó futási környezet láthatósági listája (scope chain) kerül bele (ez utóbbiról nemsokára szó esik).

Belépés egy futási környezetbe

  1. Létrejön egy speciális belső, Activation nevű objektum, melynek alapvető célja a belépési folyamat során keletkező tulajdonságok tárolása.
  2. Következő lépésként ezen belül létrejön az arguments tulajdonság, melybe a függvény számára átadott paraméterek kerülnek az átadásnak megfelelő sorrendben.
  3. Ezután beállításra kerül a láthatósági lista, mely egy objektum lista, és amely alapján zajlik majd az adott futási környezetben az azonosítók feloldása. Globális kód esetén az értéke a globális objektum lesz. Eval kód esetén a hívó futási környezet láthatósági listájának elemei kerülnek bele ugyanabban a sorrendben. Ez eddig egyszerűnek tűnik, most jön a "bonyolítás": függvény kód esetén a listába első helyre az Activation objektum kerül, majd pedig a Function objektum (amelyik tartalmazza a kódot) [[scope]] tulajdonsága által tárolt lista.
  4. Ezt követően létrejön egy Variables nevű pszeudo objektum. Ennek szerepe, hogy az ő tulajdonságaiként jönnek létre az adott függvényben definiált lokális változók, melyeknek három típusa van:
    • Függvény formális paraméterei: ezek ilyenkor veszik fel a ténylegesen átadott paraméterek értékeit. Ahol paraméter nem került átadásra, ott a neki megfelelő Variables tulajdonság undefined értéket fog hordozni.
    • Ezután következik a függvényen belüli függvény deklarációknak végrehajtása. Ezek esetében a függvény neve lesz a létrejövő tulajdonság neve. Amennyiben volt ilyen nevű formális paraméter, akkor az adott tulajdonság felülírásra kerül.
    • Legvégül jönnek a változó deklarációk. Ha már van a változó nevével egyező tulajdonság, akkor az nem kerül felülírásra. A változó deklarációk esetén a létrejövő tulajdonság értéke undefined lesz. A tényleges értéket majd csak akkor veszi fel, amikor az értékadást tartalmazó kód ténylegesen végrehajtásra kerül.
    
    function foo(bar) {
        // Próbáljuk meg kikommentezni az alábbi két sort
        function bar() {};
        var bar;
        alert(bar);
    }
    
    foo(5);
    
    Mint fentebb említettem a Variables objektum nem egy ténylegesen létező objektum, igazából a kód jellegétől függően mindig egy másik objektum testesíti meg (abban jönnek létre az említett tulajdonságok). Globális kód esetén ez a globális objektum lesz, és a tulajdonságok DontDelete attribútummal jönnek létre. Eval kód esetén a hívó futási környezet Variables pszeudo objektuma lesz a szerencsés, míg függvény kód esetén az 1. lépésben említett Activation objektumában jönnek létre a tulajdonságok szintén DontDelete attribútummal.

  5. Beállítódik a this kulcsszó értéke. Ez szintén a kód jellegétől függ. Globális kód esetén a this a globális objektum lesz. Eval kód esetén a hívó futási környezet this értékét veszi fel (pl. globális kódból hívott eval esetén ez a globális objektum). Függvény kód esetén a hívó biztosítja a this értékét, viszont ha ez nem egy objektum, akkor a this a globális objektum lesz.

Azonosítók feloldása

Egy futási környezetben az azonosítók feloldása a this kivételével úgy történik, hogy először a hozzá tartozó láthatósági lista első objektumának tulajdonságaként keresi a futtató környezet (itt is érvényes az olvasási szabály, tehát az objektumhoz tartozó prototípus láncot is megvizsgálja, ha van az objektumnak). Amennyiben ebben nem találja, akkor veszi a hivatkozási lista következő elemét. Ha a folyamat során találunk az azonosító nevével egyező nevű tulajdonságot, akkor annak referenciája lesz az azonosító, ha pedig nem, akkor null értéket fogja felvenni.

Ez az információ áradat talán egy kicsit sok volt, de egy egyszerű, de részletesen kommentezett példán keresztül könnyedén meg fogjuk tudni érteni. A program egyes pontjain nincs más dolgunk, mint felidézni az adott szituációnak, illetve kód jellegnek megfelelő szabályt/mechanizmust.

<script type="type/javascript">
    // Ezen a ponton a globális futási környezetben vagyunk, melynek
    // láthatósági listájában csak a globális objektum lesz.
    // A Variables pszeudo objektum a globális objektum lesz, melynek
    // lesz egy foo és egy bar tulajdonsága. A foo értéke itt még 
    // undefined, míg a bar értéke egy Function objektum lesz, aminek 
    // a [[scope]] tulajdonsága megkapja az aktuális futási környezet
    // láthatósági listáját, tehát abban is a globális objektum lesz.

    var foo = 5; // Itt a foo tulajdonság értéke undefined-ról 5-re változik.

    // A függvényen belüli kommentek arra az esetre szólnak, 
    // amikor az meghívódott

    function bar(x) {
        // Létrejön a futási környezet.
        // Az Activation objektumban létrejön az arguments tömb, 
        // nulladik eleme 5 lesz. A láthatósági listába bekerül
        // első helyre az Activation objektum, majd a Function
        // objektum [[scope]] tulajdonsága, tehát a globális 
        // objektum. A Variables pszeudo objektum az Activation
        // objetum lesz, melyben először megjelenik az formális
        // paraméter következtében az x tulajdonság, melynek
        // értéke 5 lesz. Ezután a belső függvény deklaráció 
        // következtében létrejön benne az innerBar nevű 
        // tulajdonság, melynek értéke egy Function objektum lesz,
        // aminek a [[scope]] tulajdonsága megkapja az aktuális 
        // futási környezet láthatósági listáját, tehát a mostani 
        // Activation objektum lesz, melyet a globális objektum követ.
        // Végül létrejön a baz tulajdonság undefined értékkel.

        var baz = 4; // A baz tulajdonság értéke 4 lesz.

        function innerBar() {};

        // A láthatósági lista alapján elő kell ásni a foo azonosító
        // értékét. Ebben most ugye az Activation és a globális 
        // objektum van. Az Activation objektumban illetve annak 
        // prototípus láncában nincs foo tulajdonság. Ugrunk a 
        // globális objektumra, amiben megtaláljuk a keresett 
        // tulajdonságot, így a foo azonosító értéke 5 lesz.

        alert(foo);
    }

    bar(5);
</script>
A fenti mankó, illetve az eddig leírtak alapján szinte bármilyen kód esetén képesek leszünk egy ehhez hasonló analízist végig vinni. Talán érdemes is egy pár próbálkozással élnünk, hogy egy kis rutint szerezzünk. Az előbbi mondatban a szinte szó csak azért szerepel, mert a fenti leírásban nem esett szó a with és a catch kulcsszavakról, melyek képesek módosítani egy futási környezeten belül a láthatósági listát, de ezekre nagyon ritkán lehet szükségünk, és nem szerettem volna a kelleténél jobban elbonyolítani a dolgokat.

Osztályszintű metódusok definiálása

Erre nagyjából annyiféle lehetőségünk van, mint ahányféleképpen függvényt tudunk létrehozni JavaScriptben: függvény deklaráció, függvény kifejezés illetve a Function objektum használata. Először is lássunk egy példát, ami mindegyik lehetőséget tartalmazza:
function outerMethod() {alert(bar);}

function classFoo()
{
	// Definiálunk egy belső változót, amin keresztül majd jól láthatjuk a különbségeket
	var bar = 5;
	//this.bar = 5;

	function innerFunction () {alert(bar);}
	this.innerMethod = innerFunction;

	this.anonymMethod = function() {alert(bar);}

	this.constructedMethod = new Function("alert(bar);")

	// Ehelyett írhatnánk ezt is: classFoo.prototype.outerMethod = outerMethod;
	this.outerMethod = outerMethod;
}

objFoo = new classFoo();
objFoo.innerMethod(); // 5
objFoo.anonymMethod(); // 5
objFoo.constructedMethod(); // bar undefined error
objFoo.outerMethod(); // bar undefined error

objFoo2 = new classFoo();
alert(objFoo.innerMethod == objFoo2.innerMethod); // false
alert(objFoo.anonymMethod == objFoo2.anonymMethod); // false
alert(objFoo.constructedMethod == objFoo2.constructedMethod); // false
alert(objFoo.outerMethod == objFoo2.outerMethod); // false
Na lássuk, hogy mi az ami érdekes a fenti kód alapján. Az innerMethod és az anonymMethod függvény objektumok esetén a korábban tanultak alapján a [[scope]] tulajdonságában benne lesz a classFoo függvényen belüli futási környezet Activation objektuma (amire ebben a jellegű kódban a Variables pszeudo objektum "mutat"), ezért látni fogják az abban definiált bar változót. Az outerMethod és a constructedMethod esetében viszont a [[scope]] csak a globális objektumot fogja tartalmazni, ezért ott a bar értéke undefined lesz. Ez alapján láthatjuk, hogy lehetőségünk van a fenti példa alapján privát változók használatára JavaScript esetén. A bar értéke kívülről nem lesz elérhető, de tudunk definiálni hozzá getBar, setBar metódusokat.

A másik érdekes dolog, hogy ha létrehozunk egy másik objektumot akkor látni fogjuk, hogy ha nem az outerMethod módszert választjuk, akkor minden egyes objektum példány esetében a metódusok számára külön függvény objektumok jönnek létre. Ha programunk elég sok objektumot használ, akkor erre érdemes lehet erre odafigyelni memóriahasználat szempontjából.

Még egy érdekességet emelnék ki. Cseréljük meg a kommentet a var bar = 5 és a this.bar = 5 sorok esetén, majd a definiált metódusokban írjuk át az alert(bar) parancsot alert(this.bar)-ra. Ha lefuttatjuk a programot, akkor azt fogjuk tapasztalni, hogy minden esetben kiíródik az 5, ebből láthatjuk, hogy a this azonosító egy függvényen belül tényleg mindig a hívó által meghatározott, a [[scope]]-pal szemben független attól, hogy milyen futási környezetben definiálták az adott függvényt.

constructor tulajdonság

A constructor egy minden egyes objektum esetén létező tulajdonság, melynek értéke az a függvény objektum, amely létrehozta az adott objektumot. Ennek több érdekes felhasználási módja is lehet:
  • Ha feldolgozzuk a tartalmát, akkor megtudhatjuk, hogy milyen típusú objektumról is van szó. Nem teszteltem sokféle böngészőn, de Internet Explorer és Firefox esetén ha saját "osztályról" van szó, akkor az értékében szövegesen benne van a konstruktor függvény.
  • Primitív adattípusok esetén is létezik ez a tulajdonság, ezért segítségével el tudjuk érni, hogy egy kapott primitív típusból a neki megfelelő objektumot hozzuk létre (5 -> Number(5)): foo = foo.construct(foo).
  • Segítségével elérhetjük az objektum prototípusát, és akár módosíthatjuk is azt, így objektum szinten van lehetőségünk az összes adott "osztályból" származó példányt módosítani anélkül, hogy tudnunk kellene, hogy pontosan melyik is ez a prototípus: objFoo.constructor.prototype.staticFoo = 5. Mint ahogy a példa is sugallja, így lehetőségünk van statikus tagváltozók létrehozására is.

isPrototypeOf metódus

A prototype objektum rendelkezik egy isPrototypeOf nevű metódussal, mellyel meg tudjuk állapítani, hogy egy adott objektum megvalósítja-e az adott "osztályt", tehát hogy a prototípus, amelyen meghívjuk, az benne van-e a paraméterül kapott objektum prototípus láncában.
function classFooParent() {}
function classFooChild() {}
classFooChild.prototype = new classFooParent;
var objFoo = new classFooChild();

alert(classFooParent.prototype.isPrototypeOf(objFoo)); // true
alert(classFooChild.prototype.isPrototypeOf(objFoo)); // true
alert(Object.prototype.isPrototypeOf(objFoo)); // true

Globális objektumban lévő objektumok kiterjesztése

Elvileg a prototype tulajdonság segítségével lehetőségünk van a "belső" objektumok kiterjesztésére is. Erre kitűnő példa a Prototype JavaScript keretrendszer, mely például egyéb rendkívül hasznos tulajdonsága mellett nagyszerűen terjeszti ki többek között a String, Array objektumokat. Viszont a törpök élete nem csak játék és mese, sajnos a csúf, gonosz Internet Explorer jelen esetben is bekavar, ugyanis az ő esetében a HTMLElement objektum (a különböző HTML elemek "ősosztálya") prototípusa nem bővíthető. Ezt a Prototype esetén az újabb verzióban (1.5.0) úgy kerülték meg, hogy a node objektumok elérésére szolgáló, document.getElementById() helyett használandó $() függvény egészítették ki úgy, hogy magát a node objektumot egészíti ki.

__proto__ tulajdonság

Bizonyos böngészők, pl. a Mozilla lehetőséget biztosít arra, hogy közvetlenül elérjük egy objekum prototípus láncát. Az objektum __proto__ tulajdonsága tartalmazza a lánc első elemét, ennek __proto__ tulajdonsága a másodikat, és így tovább egészen addig, amíg a __proto__ null értékű nem lesz. Segítségével hasznos függvényeket írhatunk, például ami megmondja, hogy egy adott objektum egy adott "osztályt" megvalósít-e:
function instanceOf(object, constructor) {
   while (object != null) {
      if (object == constructor.prototype)
         return true;
      object = object.__proto__;
   }
   return false;
}
Ez nem része viszont az ECMAScript szabványnak, azért csak adott futtató környezet (böngésző) esetén döntsük el, hogy ott támogatott-e, és csak akkor használjuk, ha biztosak lehetünk benne, hogy egyéb futtató környezetben (másik böngésző) nem kell futnia a programunknak, mert például Internet Explorer esetén ez a tulajdonság nem támogatott. Egy inkább csak érdekes felhasználási lehetőség lehet a következő:
function fooClass () {
if (!instanceOf (this, fooClass)) return new fooClass();
}
Ez esetben ha véletlenül a lehagyjuk a new kulcsszót, amikor objektumot szeretnénk használni, akkor is egy új objektumot fogunk visszakapni.

Privát attribútumok

Említettem korábban, hogy hogyan lehet privát tagváltozókat létrehozni. Ebben az esetben viszont a prototípus alapú öröklődési modell használatakor lesz egy kis gondunk.
function classFooParent()
{
var bar = 5;
this.setBar = function(_bar) {bar = _bar;};
this.getBar = function() {return bar;};
}

function classFooChild() {}
classFooChild.prototype = new classFooParent();
var foo1 = new classFooChild();
var foo2 = new classFooChild();
foo1.setBar(55);
alert(foo2.getBar()); // 55 hoppá!!!
Mint látjuk a kiírás eredménye 55 lesz, pedig eredetileg 5-öt vártunk volna. Ez azért van, mert igazából mindkét foo objektum ugyanazt a classFooParent függvény objektumot használja. Ennek elkerülése érdekében azt kell elérnünk, hogy az öröklődés ne a prototípuson keresztül történjen, amit úgy tudunk elérni, hogy explicit meg kell hívnunk a szülő konstruktorát a gyerek objektummal, amit így érhetünk el:
// Ezt a két sort le kell cserélni…
function classFooChild() {}
classFooChild.prototype = new classFooParent();

// …erre az egyre
function classFooChild()
{
classFooParent.call(this);
}
Ezzel körutazásunk végére értünk. Lehetne még sok érdekes dolgról írni a témában, például hogy milyen különböző megoldásokat lehet találni arra vonatkozóan, hogy egyszerűbben lehessen OO módon programozni JavaScriptben, de ez már nem fért bele jelen cikkbe. Remélem sokak számára lesz hasznos olvasmány, egyfajta hiánypotló írásnak szántam.
 
1

Tömény elmélet

Bártházi András · 2006. Jún. 13. (K), 14.26
Ez az írás a háttérműködés megismeréséhez nagyon hasznos olvasmány, gratula! A kérdés az, hogy az elméleti működés az egyes implementációkban vajon hogyan került implementálásra? Melyik rész lassú, melyik rész gyors Internet Explorerben? Melyik Firefoxban? Hogyan érdemes kihasználni ezeket a lehetőségeket? A háttérműködés ismerete jelent-e gyakorlati hasznosíthatóságot, vagy csak mint érdekességet érdemes megismerni?

A JavaScript objektum orientáltságának és ezirányú működésének megismerése (amiről a cikk másik fele szól) persze nagyon fontos, sajnos egyszerűen nem találok olyan embert, aki tisztában lenne ezekkel, és lenne gyakorlata kihasználásukban. Ha van valaki, jelentkezzen! :)
2

Igen, ez volt a szándék

Hodicska Gergely · 2006. Jún. 13. (K), 14.41
Igen ez most elég tömörre sikeredett, de így is elég rendesen meg kellett szűrni, hogy miket írok le. Komoly "logisztika" volt, hogy hogyan lenne érdemes a különböző fogalmakat bevezetni, hogy olyan sorrendben jöjjenek elő, hogy nagyjából szépen érthető legyen folyamatos olvasás esetén is. Még sose kellett papíron cikket terveznem. :)

Viszont a témát érdemesnek találtam erre, mert enélkül megtanulsz darabra egy JS-sel kapcsolatos szabályt, ha viszont tisztázod magadban, hogy mi is történik, akkor ez már nem is egy szabály, hanem egy evidencia.

A kérdés az, hogy az elméleti működés az egyes implementációkban vajon hogyan került implementálásra?

Amikor annó ezzel játszadoztam, akkor nem találtam olyan dolgot, ami ne a szabványnak megfelelően működött volna.

Hogyan érdemes kihasználni ezeket a lehetőségeket? A háttérműködés ismerete jelent-e gyakorlati hasznosíthatóságot, vagy csak mint érdekességet érdemes megismerni?

Van a cikkben több kapásból felhasználható gyakorlati ötlet is (lásd memória használat). Több kísérletezésre nem futotta most, így is nem is a szabadidőm ment rá, hanem alvásidőm ment rá (<tlof off>meg ma 3 óra a melóidőből ;)<tlof on>). De lehet, hogy érdemes játszadozni ilyen irányba is.


Viszont pl. a garbage collector kérdését a szabvány eléggé nyitva is hagyja, ebben komoly eltérések lehetnek böngészőkként, pl. ott az IE memory leak bug (IE nem bírkózik meg a körkörös objektum referencia hivatkozásokkal).


Felhő
3

re

toxin · 2006. Jún. 13. (K), 16.45
sajnos egyszerűen nem találok olyan embert, aki tisztában lenne ezekke


akit érdekel, két erről szóló bevezető jellegű cikk (na ilyeneket nehéz a neten találni) az katt ide

Object-Oriented Programming with JavaScript, Part I: Inheritance
http://www.webreference.com/js/column79/

ill.

Object-Oriented Programming with JavaScript, Part II: Methods
http://www.webreference.com/js/column80/

egyébként még szép hogy nehéz ehhez expertet találni, miután megismeri az ember a prototype kódbázis function osztályánák bind bővítményét ill. a class osztály bővítményeit, a függvény ill. a prototype-on alalpuló öröklődéssel az alapismeretek megszerzésén túl már nem igazán foglalkozik (szvsz).

illetve ha megnézitek pl. az Ajax in Action könyv gyakorlati példáit, a refaktorizáció során (amikor az egyszerű példák, gyakorlatban használt megoldásait tárgyalják) egy dolog lesz ismétlődő a prototype.js használata, ergo még a könyvek példái is ezen alapulnak (ezé legalábbis biztosan), nem az alap js megoldásain, na UFF :)
4

Grat!

Jano · 2006. Jún. 13. (K), 16.56
Ilyen mélységű leírással még sehol nem találkoztam, köszönet érte! Ha angolul lenne már rég le lett volna diggelve a Weblabor.
5

pedig

toxin · 2006. Jún. 13. (K), 17.27
http://jibbering.com/faq/faq_notes/closures.html

gugli és closures a varázsszó :)

ui: jöhetett volna előbb ez a cikk a akkor fentit a fr*nc se olvasta volna el :)
6

szokásos hibajelentés :)

tiny · 2006. Jún. 13. (K), 20.18
ugyanaz a szervezet (Ecma International) végezte, mint a C# vagy az Eiffel standardizálását is végezte.

Szvsz ez itt kicsit magyartalan
A JavaScriptben egy objektum alapú nyelv, melyeben minden objektum, még a függvények is.

Itt melyebent írtál és a ben rag miatt szintén magyartalan.
10

javítva

Hodicska Gergely · 2006. Jún. 13. (K), 23.17
Köszi.


Felhő
7

Gratula!

Balogh Tibor · 2006. Jún. 13. (K), 22.50
Gratula a cikkhez! Tényleg hiánypótoló! Számos dolgot segített megérteni!

Köszönet a cikk megírásáért!
8

grat

Anonymous · 2006. Jún. 13. (K), 22.51
Egy jóideje használom már az oop-s lehetőségeket JS-ben és hát eleinte igen nehéz volt a szintaktikát megszokni. (c# után...)

Ha ez a cikk akkor készült volna amikor kerestem, sok vesződéstől megkímélt volna... :)

Nagyszerű írás, grat! :)
9

ehh

connor · 2006. Jún. 13. (K), 22.52
Hogy mért felejtek el folyton bejelentkezni?!
11

gratula

amonrpg · 2006. Jún. 14. (Sze), 10.33
Jó cikk, szívből gratulálok hozzá! :)
Én is tanultam belőle! :D
12

változók elérése

Anonymous · 2006. Jún. 15. (Cs), 14.04
Nagyon jó a cikk, köszi!
Ennek kapcsán próbálgattam, megpróbáltam elérni egy függvény helyileg deklarált (var) változóit elérjem, de nem sikerült. Van erre mód? Pl elérhető valahogy a Variables pszeudo objektum?
13

ez igy helyes

connor · 2006. Jún. 15. (Cs), 17.59
A var-ral deklarált változók private ként viselkednek, így azoknak az elérése nem lehetséges kivülről* (vagyis a hatókör szerint ez a helyes működése). A public változót szeretnél akkor this.<változóneve> ként kell deklarálnod.

* Ezek olyan változok szoktak lenni aminek az értékét végig ellenőrizni akarod, hogy milyen értéket adnak a mezőnek. Ezek kulső eléréséhez szokták használni a getter/setter -eket.

function classFoo()
{
	var propFoo = 5; // private
	this.propOther = 10; //public

	this.getProp = function ()
	{
		return propFoo;
	}

	this.setProp = function (integer)
	{
		// mágikus ellenőrzések sorozata
		propFoo = integer;
	}
}

var proba = new classFoo();

alert(proba.getProp());  // 5
proba.setProp(6);
alert(proba.getProp());  // 6

alert(typeof proba.propFoo);  // undefined

// objektumnál

var o = {_name : "Start name",
         writes : 0,
         reads : 0,
         name getter : function () {this.reads++; return this._name;},
         name setter : function (n) {this.writes++; return this._name = n;}
        }

// viszont így a _name elérhető a setter nélkül is.

14

all_getter

Anonymous · 2006. Jún. 15. (Cs), 18.16
Köszi, örülök, hogy írtál, két kérdés erejéig megszólítanálak még:
(az ilyen getter/setter dolgokat ismerem, használtam már)
1: A kérdésem lényege, hogy egy olyan gettert lehet-e csinálni, ami nem konkrétan definiált (ismert nevű) változót ad vissza, hanem az összeset (mintegy tömbben)? Eddig úgy gondoltam, hogy ez nem lehetséges, mert nem tudtam arról, hogy lenne olyan objektum, ami "összefogná" a lokális változókat --- de itt jött a cikk és felrémlett valami halvány fénysugár, hogy mégiscsak, talán... (a cikkben említették a Variable pszeudo objektumot...)
2: az o deklarációjánál miért írtál "name getter"-t és miért nem csak "getter"-t?
Köszi, köszi!
15

válaszok...

connor · 2006. Jún. 15. (Cs), 19.12
1: for ciklussal bejárt objektum.

function classFoo()
{
	var propFoo = 5;
	this.a = 2;
	this.b = 3;
	this.c = 4;

	// ...
}

var proba = new classFoo();

for (i in proba)
{
	alert(i +':'+ proba[i]);  // typeof operátort érdemes itt használni...
}
2, Mert a nameként éred el kívülről (vagyis az a neve). Viszont ezzel a megoldással óvatosan mert állítólag csak gecko feature! (firefox, mozilla stb)
16

var propfoo nem jött elő

Anonymous · 2006. Jún. 15. (Cs), 19.31
2: ok, kapcsoltam, persze már értem
1: először azt hittem: hé, ez tényleg így van, akkor miért nem jöttem rá hamarabb?? De aztán kipróbáltam és nem alertezi ki a propFoo:5-öt, vagyis csak a this-szel megadott tulajdonságokat járja végig, a lokális (var - ral deklarált) változó nem elérhető így... (vagy mégis, csak valamit elnéztem?) (neked kialertezi a fenti kód a propFoo-t?)
... szóval jópofa lenne valami olyan objektumot elérni, ami a lokális változókat is tartalmazza...
17

mea culpa

connor · 2006. Jún. 15. (Cs), 20.31
Elnéztem bocs.
Szóval a példa kód sem stimmelt. Mert kívülről jártam végig. Próbálg meg metódusból!! :)
18

sajna belülről sem megy...

Anonymous · 2006. Jún. 15. (Cs), 20.56
Próbáltam ezt:

function classFoo()
 {
   var propFoo = 16;
   this.a = 2;
   this.b = 3;
   this.c = 4;    // ...
   this.ss=function()
   {
    this.d = 5;
    var propFoo2 = 26;
    for (var i in this)
    {
      alert(i +':'+ this[i]);
    }
   }
 }

var proba = new classFoo();
proba.ss();

... de sajna ez sem rakta ki a var-osokat :-(
Valami furfang kell ide, talán a cikkíró tud valami trükköt, pl. arguments.parent.valami vagy this.prototype.ize.Variables.cucc ...
Nekem tetszene! Érdekes lenne...
19

javaslom ezt

Anonymous · 2006. Jún. 15. (Cs), 22.07
Nos, megint körülnéztem a témában, és ezt találtam (és javaslom minden érdeklődőnek):
http://developer.mozilla.org/en/docs/A_re-introduction_to_JavaScript
Ahol ezt írják:
"There is no mechanism for iterating over the properties of the current scope object for example."
Mondjuk nem értem, miért nem ad lehetőséget erre a javascript...
... de ha valaki mégis tud valami trükkös megoldást, annak örülnék!
20

ilyet nem lehet

Hodicska Gergely · 2006. Jún. 15. (Cs), 23.48
A kérdésem lényege, hogy egy olyan gettert lehet-e csinálni, ami nem konkrétan definiált (ismert nevű) változót ad vissza, hanem az összeset (mintegy tömbben)?

Ez nem lehetséges. Ugye függvény kód esetén az Activation objektben jönnek létre a lokális változók (nem pedig a függvény objektum tulajdonságai lesznek), az pedig egy belső objektum, kívülről/kódból nem elérhető.


Felhő
21

És valami trükközéssel?

Anonymous · 2006. Jún. 16. (P), 00.43
Köszönöm... Sajnos erre számítottam, de valami még eszembe jutott:

Láttam egy "trükköt" :
function Person(first, last) {
    this.first = first;
    this.last = last;
}
Person.prototype.fullName = function() {
    return this.first + ' ' + this.last;
}

var s = new Person("Simon", "Willison");

alert(s);

Person.prototype.toString = function() {
    return '<Person: ' + this.fullName() + '>';
}

alert(s);


... ez elsőre meglepett, de aztán rájöttem, hogy a javascript "belsőleg" használja a toString metódust az objektum stringesítésénél, mindenféle megjelenítésénél, így ezt a folyamatot el lehet kapni. (és ezt pl. Object.prototype.toString-gel is meg lehet tenni így pl. ha valaki a deklarált lokális objektumát kiíratja, "elkaptam".)
Valami olyan mechanizmus nincsen, amit a javascript a változók "var" deklarálásakor végez? Ezt elkapva ki lehetne pakolni egy tömbbe a változók neveit, és innen már szabad a pálya...
22

Mit szeretnél pontosan elérni?

Hodicska Gergely · 2006. Jún. 16. (P), 10.14
Mert amit fentebb leírtál, az nem az a probléma, amire korábban kerested a megoldást. Szerintem írd le, hogy mit és főleg milyen cél érdekében szeretnél megoldani, és akkor hátha tudunk rá egy megoldást.


Felhő
23

Serializálás

Anonymous · 2006. Jún. 16. (P), 10.39
Szóval teljeskörű serializálást szeretnék, valami automatizmussal. Egy objektumot végig lehet járni ... de a var-ral deklarált elemeket nem sikerült még elkapni/sztringgé alakítani. Pedig nagyon érdekes lehetőségek vannak benne -- részletezzem? ... Enélkül csak olyan objektumot tudok (lehet?) serializálni, amelyik szigorúan objektum-tulajdonságokat és metódusokat tartalmaz.
Az sem baj, ha a belső változókat csak belülről lehet elérni, hiszen egy "gettert" bármikor hozzá lehet adni egy objektumhoz. A probléma az, hogy nem tudom a helyileg dekralált objektumok nevét (mondjuk "brute force"-szal meg lehetni keresni :-) ; teljeskörű serializálás nélkül valamilyen kompromisszumot kell kötni, jó lenne, ha nem kellene...
24

Nem hiszem

Bártházi András · 2006. Jún. 16. (P), 11.27
Hát, szerintem ez nem fog menni. Gondolom a JS programról szeretnél egy állapotmentést, és legközelebb folytatni onnan, ahol éppen tartott. Jobban jársz, ha "sima" objektumokat használsz erre.

Nem szeretnél regisztrálni? ;)
25

Szeretem az olyan oldalakat, ahol nem muszáj regisztrálni

Anonymous · 2006. Jún. 16. (P), 11.36
Lehet, hogy regisztrálok, csak így kényelmesebb.
Asszem csinálok valami olyat, hogy felderíti az objektumokban a "var" kulcsszót, és kigyűjti a lokális változóneveket. Ekkor már csak az eval kavarhat be, de ez is megoldható, ha átdefiniálom az eval-t... Na majd meglátom...
Mindenesetre köszi a hozzászólásokat, minden jót!
26

vélemény

inf · 2006. Júl. 17. (H), 07.19
Szija
nagyon tetszett a cikked, kaptam pár új ötletet :) de ami a legjobb benne, hogy megoldottad vele egy régi problémámat, ugyanis a prototypenak nem gondoltam volna, hogy vannak függvényei, szal nem is néztem utána, leírásokban meg nem beszéltek a témáról sehol..
a probléma nevezetesen az volt, hogy változótípusokat a typeof(valtozo) fgvel néztem eddig, csak Array -nál "object"-et ad vissza ez a fgv, nyilván mivel az Array is Object.. aztán eddig úgy oldottam meg a lekérdezését, hogy tömb e az ojjektum, hogy megnéztem, hogy vannak e tömbre jellemző fgvei..
egyébként valami hasonló közepén vagyok, mint a "Prototype JavaScript keretrendszer", picit más lesz az enyém annyiban, hogy felhasználóbarátabb, másrészt nekem kicsit más a stílusom, mint azoknak, akik azt a rendszert csinálták.. majd ha elkészültem vele, akkor írok róla bővebben, hogy hova, azt majd még meglátom :)
nah szóval köszi a cikket, nekem segített :) remélem még sokaknak fog, nagyon profi lett szvsz, csak így tovább!
27

Kis kiegeszites

w3net · 2006. Júl. 20. (Cs), 10.32
Az alabbi peldadban felrevezeto lehet a megjegyzes.

var foo = 5;

function bar(x){
var baz = 4;
function innerBar() {};

// A láthatósági lista alapján el? kell ásni a foo azonosító értékét. Ebben most
// ugye az Activation és a globális objektum van. Az Activation objektumban
// illetve annak prototípus láncában nincs foo tulajdonság.

alert(foo);
}

bar(5);

// az Activation objektumban
// illetve annak prototípus láncában nincs foo tulajdonság.

Na es ha lenne foo tulajdonsag a prototipus lancban? Semmit sem valtoztatna a dolgon, mert a prototipus lanc valtozoit csak this el lehet elerni.
Pelda:

var variable1 = 'global variable 1';

function A(){
    var variable1 = 'private variable1 A-ban';
    this.variable1 = 'public variable1 A-ban';
}

function B(){
    alert(variable1); // a globális változót irja ki, nem a prototípus láncban deklaráltat
    
    this.getVar  = function (){ // a prototípus láncból szedi ki variable1 változót
        alert(this.variable1);
    }
}

B.prototype = new A();

var oB = new B();
oB.getVar();
alert('oB.variable1 = ' + oB.variable1 ); // a prototípus láncból szedi ki variable1 változót
28

félreértés

Hodicska Gergely · 2006. Júl. 20. (Cs), 13.44
Na es ha lenne foo tulajdonsag a prototipus lancban?

Te itt az adott objektum prototípus lánácról beszélsz (meg a példakódban is erről van szó), a szövegben viszont az van, hogy az "Activation object, illetve annak prototípus lánca". A kettő nem ugyanaz.


Felhő
29

Koszonom a felvilagositast

w3net · 2006. Júl. 20. (Cs), 14.29
Egy peldat tudnal irni? Az utobbi napokban a JS OOP technikait tanulmanyozom, es ugy latom, meg mindig nem allt ossze a kep teljesen.
31

Oop

inf · 2007. Május. 22. (K), 12.24
itt írtam egy commentet, ami ehhez kapcsolódik, hátha te tudsz a jelenségre magyarázatot adni
30

Elírás

fberci · 2007. Jan. 20. (Szo), 14.57
Az osztályszintű metódusok definiálásánál a példa utolsó sorában lévő kifejezés értéke true, mint ahogy a kód utáni második bekezdésben erre utalás is szerepel.

Egyébként nagyon jó cikk, azt hiszem végre sikerült megértenem hogyan lehet használni az objektumokat a JavaScriptben.
32

Elírás

Rimelek · 2008. Május. 30. (P), 09.59
Sziasztok. Nagyon jó ez a leírás. A javascript-et már úgyahogy ismerem, de szeretnék komolyabban elmélyülni benne, és ezért örülök, hogy magyarul olvashatok arról is ami a háttérben működik.

Volt viszont egy mondat, ahol szerintem elírás lehet:
(itt is érvényes az olvasási szabály, tehát az objektumhoz tartozó prototípus láncot is megvizsgálja amennyiben az objektumnak ). Amennyiben ebben nem találja, akkor veszi a hivatkozási lista következő elemét.

Itt a zárójelben levő "amennyiben" szerintem nem oda való :)
33

Javítva

Joó Ádám · 2008. Május. 31. (Szo), 21.00
Köszönjük, javítva.