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 (
A
Minden egyes függvény objektum rendelkezik egy kitüntetett szereppel bíró 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.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.
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.
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.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 Na lássuk, hogy mi az ami érdekes a fenti kód alapján. Az
A másik érdekes dolog, hogy ha létrehozunk egy másik objektumot akkor látni fogjuk, hogy ha nem az
Még egy érdekességet emelnék ki. Cseréljük meg a kommentet a
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 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ő:Ez esetben ha véletlenül a lehagyjuk a 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 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.
■ 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 egyfor 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 anew
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 aReadOnly
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
var objFoo = new classFooChild();
classFooChild.prototype.newPropFoo = 5;
objFoo.newPropBar = 5;
alert(objFoo.newPropFoo); // 5
alert(classFooChild.prototype.newPropBar); // undefined
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 azeval()
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 aFunction
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
- 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.
- 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.
- 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 aFunction
objektum (amelyik tartalmazza a kódot)[[scope]]
tulajdonsága által tárolt lista.
- 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.
Mint fentebb említettem afunction foo(bar) { // Próbáljuk meg kikommentezni az alábbi két sort function bar() {}; var bar; alert(bar); } foo(5);
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ágokDontDelete
attribútummal jönnek létre. Eval kód esetén a hívó futási környezetVariables
pszeudo objektuma lesz a szerencsés, míg függvény kód esetén az 1. lépésben említettActivation
objektumában jönnek létre a tulajdonságok szinténDontDelete
attribútummal.
- 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ő
- 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 athis
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>
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
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
Aconstructor
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
Aprototype
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 aprototype
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;
}
function fooClass () {
if (!instanceOf (this, fooClass)) return new fooClass();
}
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á!!!
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);
}
Tömény elmélet
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! :)
Igen, ez volt a szándék
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.
Amikor annó ezzel játszadoztam, akkor nem találtam olyan dolgot, ami ne a szabványnak megfelelően működött volna.
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ő
re
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 :)
Grat!
pedig
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 :)
szokásos hibajelentés :)
Szvsz ez itt kicsit magyartalan
Itt melyebent írtál és a ben rag miatt szintén magyartalan.
javítva
Felhő
Gratula!
Köszönet a cikk megírásáért!
grat
Ha ez a cikk akkor készült volna amikor kerestem, sok vesződéstől megkímélt volna... :)
Nagyszerű írás, grat! :)
ehh
gratula
Én is tanultam belőle! :D
változók elérése
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?
ez igy helyes
* 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.
all_getter
(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!
válaszok...
var propfoo nem jött elő
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...
mea culpa
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!! :)
sajna belülről sem megy...
{
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...
javaslom ezt
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!
ilyet nem lehet
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ő
És valami trükközéssel?
Láttam egy "trükköt" :
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...
Mit szeretnél pontosan elérni?
Felhő
Serializálás
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...
Nem hiszem
Nem szeretnél regisztrálni? ;)
Szeretem az olyan oldalakat, ahol nem muszáj regisztrálni
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!
vélemény
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!
Kis kiegeszites
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:
félreértés
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ő
Koszonom a felvilagositast
Oop
Elírás
Egyébként nagyon jó cikk, azt hiszem végre sikerült megértenem hogyan lehet használni az objektumokat a JavaScriptben.
Elírás
Volt viszont egy mondat, ahol szerintem elírás lehet:
Itt a zárójelben levő "amennyiben" szerintem nem oda való :)
Javítva