ugrás a tartalomhoz

Objektumorientált JavaScript programozás a felszín fölött

Balogh Tibor · 2007. Ápr. 29. (V), 12.03
Objektumorientált JavaScript programozás a felszín fölött
Jelen cikkben a JavaScript objektumorientált programozásáról és szemléletéről szeretnék leírást adni. Bár már jelent meg Felhő tollából egy remek cikk a témában a Weblaboron, nem holmi véletlen folytán hasonlít a két cikk címe. De itt nem a JavaScript belső működése, hanem a programkód oldaláról szeretném megközelíteni a témát. A cikkben csak a JavaScript objektumorientált programozásával foglalkozhattam, mivel nem lehetett cél a JavaScript szintaktika tárgyalása, sem az objektumorientált programozáshoz tartozó fogalmak – objektum, osztály, metódus, öröklés stb. – magyarázata. Ennek ellenére, remélem, hogy mind a kezdők könnyűszerrel megértik a leírtakat, mind a gyakorlott programozók is hasznosnak fogják találni a cikket.

A JavaScript nyelv

Minden programozási nyelvet valamiféle céllal hozták létre, a JavaScript sem általános célú programozási nyelv. Azért alkották, hogy a weboldalakat és a böngészőt dinamikusan lehessen változtatni. A kliens oldali JavaScript nincs böngésző nélkül. Sőt, az a feladata, hogy a nyelvet kiszolgáló környezetet lehessen módosítani.

Még mielőtt elkezdenénk részletesebben foglalkozni a JavaScript objektumokkal, fontos áttekinteni a nyelv adattípusait. Azt, hogy milyen típusú változókat képes kezelni. Egyébként is, minden programozási nyelvvel való ismerkedéskor első lépésként az adott nyelv adattípusait érdemes áttekinteni. Már csak azért is, mert a változók adják a programozás alapját.

A JavaScript adattípusai

  1. Number: Primitív adattípus. Mind pozitív, mind negatív, egész és tört, szóval mindenféle szám tárolásához. A Number adattípus értéktartományába tartozik még az Infinity (végtelen), a -Infinity (negatív végtelen) és a NaN (nem-szám) érték is.
  2. String: Primitív adattípus szövegek tárolásához.
  3. Boolean: Primitív adattípus logikai értékek, true vagy false tárolásához.
  4. Null: Primitív adattípus a null érték tárolásához. A null elnevezés adattípust és értéket is jelent. A Null olyan adattípus, aminek egyetlen egy értéke lehet, a null.
  5. Undefined: Primitív adattípus a definiálatlan érték jelzésére. Az Undefined adattípus értéktartományába egy elem, az undefined tartozik.
  6. Object: Objektum adattípus. Az általunk definiált objektumok ebbe a csoportba tartoznak. Az alap JavaScript nyelv a következő kilenc beépített objektumtípust tartalmazza még:
    1. Number: A primitív adattípusnak megfelelő Number objektum.
    2. String: A primitív adattípusnak megfelelő String objektum.
    3. Boolean: A primitív adattípusnak megfelelő Boolean objektum.
    4. Array: Tömb létrehozására használható adattípus. A JavaScript nyelv csak egydimenziós tömböt ismer, de a tömbelemek szintén lehetnek Array objektumok, így létrehozható többdimenziós tömb. Egyébként a tömbelemek bármilyen típusúak lehetnek.
    5. Math: Matematikai objektum különböző matematikai állandók és függvények eléréséhez. A Math objektumnak nincs konstruktora, így nem hozható létre belőle több példány.
    6. Date: Dátum adattípus dátum kezeléséhez, és különböző dátumokkal kapcsolatos állandók és függvények eléréséhez.
    7. RegExp: Mintaillesztő kifejezés létrehozásához használatos adattípus.
    8. Error: Különféle hibakeresést segítő objektumok.
    9. Function: Függvények létrehozásához használatos adattípus. A függvénynév a változónévnek felel meg, és ugyanúgy viselkedik, mint bármelyik más változó.
    10. Node
      1. Document
        1. HTMLDocument
      2. Element
        1. HTMLElement
          1. HTMLHeadElement
          2. HTMLBodyElement
          3. HTMLInputElement



A HTML dokumentum objektum modellek (DOM) számos más adattípust definiálnak még. A HTML dokumentum és a JavaScript között az objektum modell a kapocs. Az objektum modell nem része az alap JavaScript nyelvnek, de a JavaScript léte szorosan összefügg a HTML dokumentumokkal, ezért érdemes itt megemlíteni ezeket az objektumtípusokat. A Node és az alatta szereplő adattípusok a DOM által definiált objektumtípusok.

Ahogy a fönti felsorolásból látható, a JavaScript hatféle alap adattípust ismer. Illetve van még plusz három, de számunkra most nem lényegesek, mivel azokat csak belső tárolásra használja a nyelv. A primitív adattípusok megegyeznek a más nyelvben megszokott típusokkal. Ha úgy tetszik, a JavaScriptben minden primitív típus, ami nem objektum. Sajnos néhány leírás éppen az Object adattípusról felejtkezik el, pedig épp a JavaScript objektumkezelése az igazán érdekes.

Ennek ellenére barokkos túlzás lenne azt állítani, hogy a JavaScript objektumorientált nyelv. A JavaScript egyáltalán nem követeli meg, hogy objektumorientáltan programozzunk, de azt mindenképpen mondhatjuk, hogy objektumalapú. Nem azért mert minden változó (valójában tulajdonság) objektum lenne, hanem azért mert a legfelső szinten objektumot találunk. A JavaScript primitív adattípusai nem objektumok. Ezen felül a legtöbb primitív adattípusnak létezik objektum megfelelője is, és a JavaScript – nem típusos nyelv lévén – automatikus típuskonverziót végez.

A weboldalak elemeit is objektumokon keresztül érhetjük el. A HTML dokumentumok objektum leírása, a Dokumentum Objektum Modell (DOM) azt írja le, hogy milyen szabványos objektumokkal kezelhetjük a weboldalak elemeit, és azok hogyan viselkednek. Amikor sokan szidják a JavaScript nyelv miatt ezt vagy azt a böngészőt, akkor jórészt – tudatosan vagy sem – az adott böngésző JavaScript DOM implementációjáról mondanak véleményt. Általában két eset fordul elő: vagy az, hogy a böngésző egyszerűen meg sem valósít egy objektumot vagy metódust (Opera), vagy a böngésző külön utakon jár (Internet Explorer), ami alapján nem a DOM specifikáció szerint érhetjük el a weboldalelemeket. A böngészők az alap JavaScript szintaktikában és értelmezésben megegyeznek. A Microsoft JScript és a Netscape JavaScript nyelv is hasonlóan működik, ahogy az az ECMAScript specifikációban szerepel.

Ezek után már meg tudjuk határozni, hogy hol helyezkedik el a JavaScript nyelvben az Object objektumtípus. Hozzákezdhetünk közelebbi barátságot kötni ezzel az adattípussal.

Objektum a kimeneten

Minden bizonnyal előfordult már, hogy szerettük volna elérni, hogy egy függvény egyszerre több értékkel térjen vissza. Általában akkor fordul elő ilyen helyzet, amikor olyan adatokat várunk, amik szorosan egymáshoz tartoznak. A JavaScriptben használhatjuk erre az objektumokat, amik név-érték párok csoportjai.

Az egér pozíciójának lekérdezése

Az egyik ilyen függvény, ahol szükségét láthatjuk annak, hogy objektummal térjen vissza, az egérpozíció lekérdezését végző függvény lehet. Ahol egyszerre kell visszaadnunk mindkét koordinátát.
function getMouse(e){
   e = e || window.event;

   //Egy új objektumot hozunk létre.
   var mouse = new Object();

   //A mouse nevű objektum x és y tulajdonságának definiálása
   mouse.x = (e.pageX || e.clientX);
   mouse.y = (e.pageY || e.clientY);

   //Visszatérünk az objektummal
   return mouse;
}

//A getMouse függvény használatának bemutatása
document.onclick = test;
function test(e){
   var m = getMouse(e);
   alert( "Az egér pozíciója: " + m.x + "×" + m.y );
}
Az első sorban az e változó értéke a függvény meghívásakor aktuális esemény (event) lesz. Azért van rá szükség, mert az egér tulajdonságait, így az egér koordinátáit is az Event objektumokon keresztül érhetjük el. A második lépésben létrehozunk egy üres objektumot. Majd a következő két sorban értéket adunk az objektum x és y tulajdonságának. A JavaScript, nem típusos nyelv lévén, az objektum tulajdonságait az értékadáskor létrehozza. Majd ezzel az objektummal – ami tartalmazza az egér x és y koordinátáit – tér vissza a függvény.

Az objektum literál

Látható, hogy egyáltalán nem bonyolult új objektumot létrehozni, és használatukkal nem csak a függvény működése átláthatóbb, de a visszakapott értékkel is könnyebb dolgozni. Szerencsére megfogtuk az Isten lábát, mert a JavaScript alkotói – igazodva ahhoz, hogy a JavaScript egy szkript nyelv – számos esetben többféle szintaktikát is lehetővé tettek. Létrehozhatunk objektumokat objektum literállal is, ami az előbbinél olvashatóbb formátumot eredményez. Objektumra a kapcsos zárójelekkel ({}) hivatkozhatunk.
function getMouse(e){
   e = e || window.event;

   //Ez a rész ugyan azt eredményezi, mint az előző függvény programsorai.
   return {
      x : (e.pageX || e.clientX),
      y : (e.pageY || e.clientY)
   }
}

//A getMouse függvény használatának bemutatása
document.onclick = test;
function test(e){
   var m = getMouse(e);
   alert( "Az egér pozíciója: " + m.x + "×" + m.y );
}
A return után létrehozunk egy objektumot, amiben fölsoroljuk az objektumhoz tartozó név-érték párokat. Az objektum literálban az értékadáshoz az egyenlőségjel helyett a kettőspont használatos, a név-érték párokat pedig vesszővel kell elválasztanunk. Így egy lépésben létrehozzuk az objektumot, és értéket adunk az x és y tulajdonságoknak is. A létrehozott objektumot tárolhatnánk egy változóban is, ahogy az első példában tettük. De jelenleg a függvényben semmit nem kezdünk vele, ezért a return rögtön visszatérhet az előállított eredménnyel.

Literál

A programban szereplő nem nevesített konstanst jelenti. A fönti kódrészletben sztring literál az "Az egér pozíciója: " szöveg. Egyébként betű szerinti alakot jelent. A JavaScript objektum literálja nem minden esetben igazi literál, a tulajdonságértékek meghatározásakor a kifejezések futásidőben értékelődnek ki.

Objektum a bemeneten

A getMouse függvény hívásakor egy könnyen kezelhető visszatérési értéket kaptunk. Ugyanígy használhatunk objektumot függvényparaméterként is. Számos esetben előfordul, hogy nem tudjuk előre a függvény paramétereinek számát, változó paraméterlistára lenne szükség. Erre az egyik megoldás, hogy a függvény hívásakor automatikusan létrejövő arguments változót használjuk a függvény paramétereinek kinyeréséhez. A másik megoldás lehet, hogy objektumot alkalmazunk paraméterként.

HTML elemek tulajdonságainak megváltoztatása

A weboldalak módosítása során elég gyakran előfordul, hogy meg kell változtatnunk a weboldal egy elemének a tulajdonságait. Mivel gyakran ismétlődő műveletről van szó, érdemes fölemelni egy magasabb absztrakciós szintre. Készítsünk hozzá egy függvényt, ami paraméterül egy weboldal elemet és a megváltoztatandó tulajdonságokat kapja.
function setAttribs(element, attribs){
   //Végiglépkedünk az attribs objektum elemein
   for (var i in attribs){
      if (typeof attribs == "object"){
         //Az elem szintén objektum, meghívjuk erre is a függvényt
         setAttribs(element[i], attribs[i]);
      }else {
         //Az elem adott tulajdonságának megváltoztatása.
         try{ element[i] = attribs[i]; } catch(err){}
      }
   }
}

//A setAttribs függvény használatának bemutatása
var testElement = document.getElementById("header_element");
setAttribs(testElement,
   {
      title: "Ez egy felirat",
      style:
      {
         cursor: "help",
         "text-decoration": "underline"
      }
   }
);
A setAttribs függvény hívását több sorba törtem, hogy a második paraméter felépítése jobban látható legyen. A függvény első paramétere rendszerint a weboldal egy eleme, az attribs paraméter pedig egy objektum, ami az első paraméter beállítani kívánt tulajdonságait tartalmazza. A példában értéket adunk a megadott HTML elem title tulajdonságának, és megváltoztatjuk a stílusát.

A JavaScript szintaktika lehetővé teszi, hogy egy tulajdonságot ne csak a pont (.) operátorral érjük el, hanem hivatkozhatunk rá kapcsos zárójelekkel ([]) is. Ebben az esetben a tulajdonságnevet megadhatjuk sztringgel és változóval is. Ha nem ismerjük a tulajdonság nevét – ahogy a setAttribs függvény esetében – akkor ez utóbbi szintaktikát kell alkalmaznunk.

A függvény első sorában egy for in ciklust indítunk, végiglépkedünk az attribs objektum elemein. Ha az attribs aktuális eleme objektum, akkor rekurzívan meghívjuk arra az objektumra a setAttribs függvényt. Ha az érték nem objektum, akkor az element adott tulajdonságára megpróbáljuk beállítani az értéket. A paraméterként átadott attribs objektum név-érték pároknak meg kell felelni az element objektum tulajdonságnév-érték párjainak, ahogy ez a függvény hívásánál látható.

A Weblabor ikonsorának beállítása

A Weblaboron az ikonsor diszkréten jön létre. Csak akkor jelennek meg a plusz ikonok, ha be van kapcsolva a JavaScript. A setAttribs függvényt használva átláthatóbb és könnyebben módosítható eszköztárat létrehozó függvényt alkothatunk.
function addIcons(){
   //Az oldalon az ikonokat a toolset azonosítójú div elem tartalmazza.
   var area = document.getElementById("toolset");
   if (!area) { return; }

   //Az ikonok felsorolását egy tömb tartalmazza.
   //A tömb elemei pedig objektumok, amik az ikonokat írják le.
   var icons = [
      {
         src    : "/themes/azigazi/print.gif",
         alt    : "Nyomtatás",
         title  : "A dokumentum nyomtatása",
         id     : "tool-print",
         style  : {
            cursor:"pointer",
            "font-size":"25%"
         },
         onclick: printPage
      },
      {
         src    : "/themes/azigazi/bigfont.png",
         alt    : "Nagyobb betűméret",
         title  : "Nagyobb betűméretre váltás",
         id     : "button-bigfont",
         style  : {cursor:"pointer", "font-size":"25%"},
         onclick: function(){ setStyle("style-bigfont"); }
      },
      {
         src    : "/themes/azigazi/smallfont.png",
         alt    : "Normál betűméret",
         title  : "Normál betűméretre váltás",
         id     : "button-smallfont",
         style  : {cursor:"pointer", "font-size":"25%"},
         onclick: function(){ setStyle("style-smallfont"); }
      }
   ];

   //Végiglépkedünk a tömb elemein, létrehozunk egy img elemet, és hozzáadjuk az ikonsorhoz.
   for (var i in icons){
      area.appendChild( createElement("img", icons[i]) );
   }
}

function createElement(type, attribs){
   //Adott típusú elem létrehozása, majd beállítjuk a megadott tulajdonságokat.
   var e = document.createElement(type);
   setAttribs(e, attribs);

   return e;
}
Dinamikusan hozzuk létre az ikonokat, ezért készítettünk egy createElement függvényt. Ez azzal több a document.createElement metódusnál, hogy az elemhez beállítja a megadott tulajdonságokat is.

Az addIcons függvény pedig bár hosszúnak tűnik, de valójában három sorból áll. Az ikonok leírását az icons tömb tartalmazza, a tömb elemei objektumok, amik a képek leírásait tartalmazzák. A setStyle és a printPage függvények szerepelnek a Weblabor JavaScript fájljában. De a paraméterátadás miatt a setStyle hívásához nem nevesített burkoló függvényeket hozunk létre.

Az ikonoknak ezt a hajlékony és könnyen átlátható leírását az teszi lehetővé, hogy a setAttribs függvény paraméteréül, a tulajdonságok meghatározásához objektumot használtunk.

Hogyan hozzunk létre osztályt?

Most már egészen jól tudjuk alkalmazni a JavaScript objektumokat visszatérési értékként és paraméterként is. Miután nem elégedünk meg az objektumok közvetlen létrehozásával, fölmerül a kérdés: hogyan kell osztályt létrehozni?

A válasz nagyon egyszerű: sehogy. A JavaScript nyelv nem ismeri az osztály fogalmát. Nem tudunk osztályt létrehozni, mivel nincs ilyen.

Az első fejezetben tárgyaltuk, hogy a JavaScript nyelv célja, hogy módosíthassuk a weboldalak, illetve a böngésző elemeit, ezeket az elemeket pedig objektumokon keresztül érhetjük el. A JavaScript megalkotói szembekerülhettek azzal a problémával, hogy hogyan lehet módosítani az elemek viselkedését, miközben megtartsák az objektum alapokat. Amikor megnyitunk egy weboldalt, és a JavaScript kód fut, az aktuális weboldal elemei, mint objektumok már a JavaScript rendelkezésére állnak, vagy még be sem töltődött az oldal, ezért egyáltalán nincsenek jelen. Azt kellett feloldani, hogy egy osztály módosításakor a módosítások érvényre jussanak az osztálypéldányokon is. A JavaScript válasza a problémára az, hogy nincs osztály.

Objektumorientált programozási szemléletek

Valahol olvastam a következő mondatot:

Aki látott egy asztalt, az látta valamennyit.
Platón ideák tana és az objektumorientált programozás
A mondat egyikféle értelmezése a platóni filozófiából, az ideák tanából ered. Gondolati világunkban minden létezik, aminek végső eredője a szellemi világ. Amikor valaki kitalál, felfedez, vagy megalkot valamit, tulajdonképpen visszaemlékszik az isteni ideára. A gondolati világban létezik, létezett és létezni fog „AZ Asztal”. Ellentétben a fizikai világ asztalaival. Az objektumorientált programozásban egy asztal osztály „AZ Asztal” ideának a leírása.
A fizikai szemlélet
A másik, Platón szemléletétől eltérő megközelítés a fizikai szemlélet. Ha már láttál egy asztalt – mivel mindegyik valamiképpen hasonlít egymásra – a többit is fölismered. Ezért bármelyik asztal megismerése után felismerjük a többit is, ha a szemünk elé kerül. A feltétele a felismerésnek, hogy megismerkedjünk a fizikai világunkban az adott dologgal. A JavaScript objektumorientált szemlélete – hogy elhagyhassa az osztály fogalmát – ez utóbbi megközelítést követi. A JavaScript azt mondja, hogy:

Mutass egy objektumot, ilyen a többi is. Ha mégsem ilyen, akkor majd mondd meg, hogy miben különbözik!

Objektumok gyártása

Ha nem közvetlenül, objektum literállal – ahogy azt a bevezető fejezetekben tettük – akarjuk létrehozni az objektumokat, akkor az objektum előállításához a new operátort kell használnunk. Bármelyik függvény végrehajtásával előállíthatunk objektumot, ha a függvényt a new operátorral hívjuk meg. A függvény ekkor az objektum konstruktora (készítője, előállítója) lesz.

A konstruktor, az objektum előállítója

A következő sorok üres objektumokat állítanak elő, amiből csupán a konstruktorhívás, az objektumlétrehozás szintaktikája látható.
//Objektum előállítása objektum literállal.
var obj1 = {};

//Objektum előállítása a beépített Object konstruktorral.
var obj2 = new Object();

//Objektum előállítása saját konstruktorral.
function constructorEmptyObject(){ }
var obj = new constructorEmptyObject();

//Egy sorba tömörítve.
var obj = new function constructorEmptyObject(){};
Bármelyik függvényt meghívhatjuk a new operátorral, akár a setAttribs függvényt is. De a setAttribs függvény nem konstruktornak készült, ezért konstruktorként meghívva – ahogy a példában szereplő constructorEmptyObject esetében is – csak üres objektumot fogunk visszakapni. Ha egy függvényt, amit konstruktornak készítettünk, a new operator nélkül hívunk meg, az eredmény a konstruktortól fog függeni, és a konstruktor mint függvény fog végrehajtódni. Meghívhatjuk a beépített String konstruktort is a new operátor nélkül. A String("Egy sztring") kifejezés egy primitív sztringet fog visszaadni, nem objektumot.
//String objektum előállítása.
var objStr = new String("Egy sztring");

//Primitív String típust ad vissza.
var prmStr = String("Egy sztirng");

//Object típusú objektumot állít elő.
var objObj = new constructorEmptyObject();

//A változó értéke undefined lesz, mivel nem határoztuk meg a visszatérési értéket.
var unDef = constructorEmptyObject();

A tulajdonságok meghatározása

Erősen korlátozott az üres objektumok használata, amik semmilyen tulajdonsággal nem rendelkeznek. A konstruktorokban megadhatjuk az előállított objektum tulajdonságait. A konstruktorként használt függvényben az előállított objektumra a this ([i]ez) szóval tudunk hivatkozni.
function constructorObject(value){
   //A konstruktorban a this szóval az éppen előállított objektumra hivatkozunk.
   this.foo = 2;
   this.bar = value;
}
var obj = new constructorObject(5);
alert( obj.foo + obj.bar );

A metódusok definiálása

A cikk elején, az adattípusok leírásánál láthattuk, hogy a függvények is egyfajta változók, Function típusú objektumok. A konstruktorban ugyanúgy definiálhatunk metódusokat, ahogy tulajdonságokat. Illetve minden olyan objektumtulajdonság metódus lesz, aminek a típusa Function.
function constructorObject(value){
   //Egy foo nevű tulajdonság meghatározása a konstruktorban.
   this.foo = value;

   //Egy addFoo metódus definiálása a konstruktorban.
   this.addFoo = function(plus){
      this.foo += plus;
   }
}
Most már képesek vagyunk meghatározni az objektumok tulajdonságait és metódusait. Elő tudunk állítani bonyolultabb objektumokat is. Ennek ellenére Objektumorientált Programozásról még nem beszélhetünk, az OOP-hoz legalább két dolgot kell megvalósítani a programozási nyelvben:

  1. Objektumok létrehozása tulajdonságokkal és metódusokkal
  2. Öröklés megvalósítása

Ha ez a két dolog megoldott a nyelvben, akkor beszélhetünk arról, hogy a nyelv támogatja az objektumorientált programozást. Az OOP további lehetőségei programozástechnikai dolgok, amiket vagy megvalósít egy nyelv, vagy nem. A szokásos objektumorientált nyelvekben az öröklés osztályok között történik, mivel az osztályok az objektumok sablonjai. De a JavaScript nem ismeri az osztály fogalmát, ezért ez a fajta megoldás nem jöhet szóba.

A prototípus szerinti objektumkezelés

Minden függvény rendelkezik egy prototype (mintadarab, prototípus) nevű tulajdonsággal, ez a tulajdonság vagy Object vagy Null típusú értéket tartalmaz. A prototype akkor kap jelentőséget, ha a függvényt a new operátorral hívjuk meg, azaz amikor a függvény konstruktorként működik. A prototype tartalmazza az objektum előállításához használatos mintadarabot, aminek megadásával azt mondjuk a JavaScript számára, hogy

Íme egy objektum, így néz ki a többi is. Ha mégsem ilyen lenne, majd megmondom, miben különbözik.
//Konstruktor Person típusú objektumok létrehozásához.
function Person(familyName, foreName){
   //A megadott név tárolása
   this.name = [familyName, foreName];
   //Egyéb tulajdonságok beállítása
   this.count.index += 1;
   this.index = this.count.index;
}

//A mintadarab megadása a konstruktor prototype tulajdonságával.
Person.prototype = {
   //A tulajdonságok bármilyen típusúak lehetnek.
   name : null,
   status : 1,
   //Néhány további tulajdonság megadása.
   index : 0,
   count : { index : 0 },
   constructor : Person,
	 
   //Az objektum metódusai.
   setName : function(familyName, foreName){
      //A metódusokban vigyázva a this szó használatával! Erről majd később.
      this.name = [familyName, foreName];
   },
   getName : function(){
      //A teljes név visszaadása, igaz az objektumok
      //tulajdonságai publikusak, azaz közvetlenül is elérhetők.
      return this.name[0] + " " + this.name[1];
   }
};

//Két Person típusú objektum létrehozása.
var person1 = new Person("Csavardi", "Samu");
var person2 = new Person("Zsákos", "Frodó");

//Példák a tulajdonságok elérésére és metódusok hívására
alert( person1.index + " : " + person1.getName() + " : " + person1.status );
alert( person2.index + " : " + person2.getName() + " : " + person2.status );
A mintadarab nem osztály
Fontos, hogy sem a konstruktor, sem a konstruktor prototype tulajdonsága nem azonos a megszokott osztály fogalmával. Bár, ha könnyebb számunkra, akár gondolhatunk rá így is. Az osztály minden esetben sablon. Egy általános leírás arról, hogy az adott objektum hogyan működik, és milyen tulajdonságai vannak. Konkrét értékeket nem tartalmaz. Ha mégis, az magára az osztályra vonatkozik, és nem az objektumra, vagy a példányosítás megkönnyítése miatt tartalmazza egyes tulajdonságok alapértelmezett értékeit.

Egy user osztály számos tulajdonság és metódus definícióját tartalmazhatja. Meghatározhatjuk például azt, hogy a példányosított osztálynak lesz hajszín nevű tulajdonsága, de az osztály nem tartalmazza azt, hogy milyen színű az a haj.

A prototype a nevének megfelelően egy mintadarabot tartalmaz, tulajdonságokkal, metódusokkal és értékekkel együtt.
A prototípus lánc
Egy objektum létrehozásakor, még a konstruktor futása előtt létrejön egy belső hivatkozás az éppen előállított objektum és a prototype tulajdonságban tárolt objektum, azaz a mintadarab között. A fenti példánál maradva a status tulajdonságot csak a prototype-nál definiáltuk. Amikor a kódban hivatkozunk az person1.status tulajdonságra, akkor a JavaScript előbb megnézi, hogy maga a person1 objektum rendelkezik-e ilyen tulajdonsággal. Ha nem, akkor megvizsgálja, hogy az objektumhoz rendelt prototype rendelkezik-e vele, és mivel a prototype is egy objektum, és ahhoz az objektumhoz is tartozik egy belső prototype hivatkozás, ami vagy szintén objektum vagy null érték, a JavaScript tovább folytathatja a sort, amíg a végére (elejére) nem ér. Ha ebben a láncban megtalálja a keresett tulajdonságot, akkor visszatér az értékével.

Így a mintadarab tulajdonságai a létrehozott objektumok tulajdonságai is lesznek. A prototype – illetve az objektumhoz tartozó prototípus lánc – felelős az adott objektumok között a tulajdonságok megosztásáért. A person1 és a person2 objektumok status tulajdonsága ugyanazon értéket adják vissza, mert a konstruktoruk prototype.status tulajdonságát jelentik.
A prototípus szerinti öröklés
Ez a belső prototípus lánc teszi lehetővé – a JavaScript ezen keresztül valósítja meg – az öröklést. Ha tovább szeretnénk örökíteni az adott objektumot, vagy még inkább, ha tovább szeretnénk fűzni a prototípus láncot, akkor a konstruktor prototype tulajdonságának az örökíteni kívánt objektumot kell értékül adnunk. Így a konstruktor prototype tulajdonsága egy belső hivatkozást fog tartalmazni az örökített objektum – normál objektumorientált nyelvben ez a szülő osztály – mintadarabjára.

//A kiterjesztett objektumok konstruktora.
//Minden olyan beállítást, amit az örökölt objektum konstruktorában kellene megadnunk,
//itt is meg kell adnunk, vagy közvetlenül nekünk kell meghívnunk az előző konstruktort.
function VIP(familyName, foreName){
   this._proto_.constructor.call(this, familyName, foreName);
   this.status = 1;
}

//A prototípus lánc továbbfűzése.
VIP.prototype = new Person();

//A mintadarab további tulajdonságainak megadása.
//Itt nem használhatunk objektum literált, különben felülírnánk az előző objektumot.
VIP.prototype.getStatus = function(){
   return this.status;
}

//Megadunk egy hivatkozás az előző prototípusra.
VIP.prototype._proto_ = Person.prototype;

//Egy VIP típusú objektum előállítása.
var person3 = new VIP("Szürke", "Gandalf");
alert( person3.index + " : " + person3.getName() + " : " + person3.status );
Első lépésben a konstruktor VIP.prototype tulajdonsága egy Person objektum lesz. Így kapcsolódik a VIP.prototype objektum a Person prototípus láncához. A VIP.prototype objektumot a továbbiakban bővíteni nem tudjuk objektum literállal, különben fölülírnánk a new Person() kifejezés által meghatározott értéket. A prototípus további tulajdonságainak – például azoknak a tulajdonságoknak, amiket pluszban hozzá szeretnénk adni – a pont (.) operátorral tudunk értéket adni.

Figyelem! A Person konstruktor csak egyszer hajtódik végre. Akkor, amikor – egy objektum létrehozásával – kapcsolódtunk az adott prototípus lánchoz. De a továbbiakban, a VIP típusú objektumok létrehozásakor a Person konstruktor nem fog végrehajtódni. Az objektumok létrehozásakor csak egy konstruktor fut le. Ezért, ha szükséges az előző prototípus konstruktorát nekünk kell közvetlenül meghívnunk.

Így kapcsolódik a VIP.prototype objektum a Person prototípus láncához, és egyben saját további tulajdonságokkal is rendelkezhet, amiket az alatta lévő objektumokkal oszt meg. Ennél a példánál maradva, a prototípus lánc így néz ki:

Object.prototype
   |
   - Person.prototype
        |
        - person1
        |
        - person2
        |
        - VIP.prototype
            |
            - person3


Így a prototípus láncon keresztül a person3 objektum hozzáfér mind a VIP konstruktor, mind a Person konstruktor, mind az Object konstruktor prototype objektumának tulajdonságaihoz. Ezek a tulajdonságok mind úgy fognak viselkednek, mint a person3 saját tulajdonságai. Ezen kívül a person3 nem fér hozzá az person1 vagy person2 tulajdonságaihoz, vagy egyéb objektumokhoz, és a person1 objektum sem fér hozzá a VIP konstruktor prototype objektumához, sem a person3 objektumhoz, mivel ezek vagy ugyanazon a szinten vannak, vagy alatta helyezkednek el a prototípus láncban.

A programkódban a legfelső, Global típusú objektumtól indulva explicit módon bármelyik objektum – nem Internal – tulajdonsága elérhető. A böngészőkben ez a window objektum. Természetesen az előbb implicit hivatkozásokról volt szó. A hivatkozások feloldásával nem nekünk kell foglalkoznunk. Az objektumok által látott tulajdonságok az objektumok saját tulajdonságaként viselkednek.

Amiért elfelejtkezett a JavaScript az osztályról

A JavaScriptet eredetileg a weboldalak és a böngészők dinamikusabbá tételéért hozták létre. Ami azt jelenti, hogy ellentétben a többi nyelvvel, a JavaScriptnek már egy kész rendszerben kell tevékenykednie, és az őt kiszolgáló környezet elemeit módosítania. A JavaScript futásakor a weboldal már felépült, vagy egyáltalán nem is létezik még a JavaScript részére.

Akárhogy is, de a JavaScript csak a betöltött és felépített oldalelemekhez férhet hozzá. A szokásos OOP módon nem megoldható a feladat, hogy már kész objektumokat lehessen módosítani az osztályokon keresztül. A szokásos OOP nyelvekben sem az osztálypéldányon keresztül nem változtatható meg az osztály, sem az osztálypéldány nem fog megváltozni az osztály módosításakor.

Ezt a problémát oldja meg a prototípus szerinti objektumkezelés. Ami azt mondja, hogy nincsenek osztályok, hanem minden egy nagy objektum része. Az objektumokból alulról láthatók a fölöttük lévő elemek, az adott prototípus lánchoz tartozó mintadarabok tulajdonságai és metódusai. A prototípus lánc valósítja meg az öröklést és a tulajdonságok megosztását. Tulajdonképpen, mint láthattuk, az öröklés is a tulajdonságok megosztásán keresztül valósul meg.

Ha megváltozik egy prototípus, akkor a prototípus lánchoz kapcsolódó objektumok is a megváltozott tulajdonságokat fogják látni. Mivel azok ebben az esetben nem a saját tulajdonságukat, hanem a minta-objektum tulajdonságát használják. Ha pedig az megváltozik, akkor érthető módon az objektumlánc többi tagja is az aktuális tulajdonságértéket fogja megkapni.

Az objektumlánc legfelső helyén az Object.prototype tulajdonság által leírt objektumot találjuk. Ezért ezen objektum prototype tulajdonságának módosítása minden JavaScript objektumra hatással van. A módosítás mind a prototype tulajdonság módosítása után, mind a módosítása előtt létrehozott objektumokra ki fog hatni.
//Hivatkozás a HTML oldal html elemére.
var html = document.getElementsByTagName("html")[0];

//A tulajdonság értéke undefined, mert nem definiáltuk.
alert( html.MAX_VALUE );

//Az Object prototype bővítése.
Object.prototype.MAX_VALUE = 1;

//A tulajdonság már nem undefined, mert a prototípus láncában már fellelhető a tulajdonság.
alert( html.MAX_VALUE );

//Ennek nem 1 az értéke.
alert( Number.MAX_VALUE );
A Number objektumhoz – a Number típus konstruktorához – alapértelmezetten definiálva van a MAX_VALUE tulajdonság. Ezért amikor arra hivatkozunk, a JavaScript nem keresi tovább a tulajdonságot a prototípus láncban, mivel közvetlenül megtalálta az objektumnál. A Number objektumhoz meghatározott MAX_VALUE elfedi a prototípus láncban szereplő értéket.

Természetesen nem csak az Object.prototype mintadarabot módosíthatjuk, bármelyik JavaScript objektum prototípusát megváltoztathatjuk, amivel elérhetjük, hogy a többi ugyanolyan típusú objektum is a megváltozott tulajdonságot fogja látni.

Alap JavaScript objektumtípusok bővítése

Különféle tulajdonságokkal és újabb metódusokkal bővíthetjük a beépített objektumtípusokat is. A példában a Date típusú objektumokat egy hasznosnak vélt metódussal egészítjük ki.
Date.prototype.getJD = function(){
   //Az aktuális értékek kinyerése, a hónapok sorszámozása 0-val kezdődik.
   var y = this.getFullYear(),
   m = this.getMonth()+1,
   d = this.getDate(),

   jd = 367*y-7*(y+(m+9)/12)/4+(275*m/9)+d+1721027;
   if  (jd > 2299170) jd += 2-3*((y+(m-9)/7)/100+1)/4; /*2299170=>1582.10.04.*/
   return parseInt(jd);
}
A getJD metódus az adott dátumot konvertálja át Julian date értékre. A Julian date-nek semmi köze a Julianus naptárhoz, annál inkább Joseph Justus Scaliger csillagászhoz. A Julian date a Kr.e. 4712.01.01.-től eltelt napok számát adja meg, figyelembe véve a Gregorian naptárra való átállást is. Az értéket a csillagászatban periodikus események vizsgálatához használják. Hasonlatos a Unix időbélyeghez, csak a Julian date nullpontja a Kr.e. 4712.01.01. dátum, és a mértékegység nem másodperc, hanem nap. Ha másodperc-időbélyeg helyett nap-időbélyegre van szükségünk, inkább használjuk a Julian date értéket.
//A prototípus kiegészítése után a dátum típusú változóknál használhatjuk a metódust.
var d = new Date;
alert( "Kr.e.4712.01.01. óta eltelt napok száma: " + d.getJD() );

HTML elemtípusok bővítése

Már meg tudjuk változtatni a JavaScript beépített objektumtípusait, de a JavaScript oldaláról nézve a weboldal elemek is ugyanolyan objektumok, mint az alap objektumok. Azaz ugyanúgy módosíthatóak, és a prototípus lánc segítségével a módosítások érvényre jutnak.
//A táblázatcellák prototípusához megadunk egy Message tulajdonságot.
HTMLTableCellElement.prototype.Message = "%1. táblázatcella";

window.onload = function(){
   //A táblázatok minden cellájához megjelenítjük a prototípusnál megadott szöveget.
   var c = document.getElementsByTagName("td");
   for (var i in c){
      c.onclick = function(){
         alert( this.Message.replace(/%1/, this.cellIndex+1) );
      }
   }
}
„A törpök élete nem csak játék és mese”, a csúf, gonosz Internet Explorer nem támogatja a DOM prototípusok bővítését. Már csak annál az egyszerű oknál fogva sem tehetjük ezt meg, mivel maguk a Node, Document, Element, HTMLElement stb. objektumok sem találhatóak meg a window objektumban, így aztán nehéz is ezen objektumok prototype tulajdonságát megváltoztatni.

Melyiket szeressem?

Prototype vagy konstruktor

Eddig eljutva jó rálátásunk lehet a JavaScript objektumkezelésére, és minden bizonnyal feltűnt, hogy kétféle módon határozhatjuk meg az előállítandó objektum tulajdonságait.

  • A prototype tulajdonságon keresztül
  • A konstruktorban a this kulcsszóval

A prototype alkalmazásakor az objektum tulajdonságára való hivatkozáskor – ahogy azt az előbb megismertük – a JavaScript megvizsgálja az objektumot, hogy megtalálható-e az adott tulajdonság, mivel ott nem találja meg, folytatja a keresést a prototípus láncban.

Ha az objektumhoz a konstruktorban a this kulcsszóval adjuk hozzá a tulajdonságot, és amikor hivatkozunk rá, a JavaScript az objektumnál megtalálja, nem keresgél tovább a prototípus láncban. Ellenben ezt a módszert alkalmazva nem tudjuk egy lépésben – a prototype módosításával – megváltoztatni a tulajdonságot, mert a létrehozott objektumok elfedik azt.

A prototype használatával memóriát takaríthatunk meg, és az objektumok is gyorsabban hozhatók létre, de a tulajdonságok elérése – ha nem is lényegesen, de – lassabb lehet. Ha a tulajdonságokat a konstruktorban definiáljuk, akkor az objektumok gyártása lassabban történik, és több memóriát is igényel ez a fajta módszer.

Mindezek a JavaScript prototípus szerinti objektumkezeléséből következnek. A fejezet végére érve írtam egy tesztszkriptet. A Firefox böngésző alatt mért eredmények a leírtakat hozták, igaz a két módszer között nem volt számottevő különbség. De figyelembe véve, hogy a prototype használatával kevesebb memóriát fogyasztunk, könnyebben módosítható objektumokat kapunk, és nem utolsó sorban, objektum literál alkalmazásával átláthatóbb a kódunk, ezért a prototype tulajdonság használata javasolt.

A prototípus lánc és a konstruktor

Befolyásolja-e a prototípus lánc továbbfűzését, ha a fölső objektumnál nem használjuk a prototype tulajdonságot, és a konstruktorban a this kulcsszóval hozzuk létre a tulajdonságokat? Ha figyelmesen olvastuk el a tulajdonságok megosztása részt, akkor a választ már tudjuk, ezért mindenféle magyarázat helyett, íme egy példakód:
//Person konstruktor
function Person(familyName, foreName){
   //Minden tulajdonságot itt adjuk meg, nem prototípussal.
   this.name = [familyName, foreName];
   this.status = 1;

   this.setName = function(familyName, foreName){
      this.name = [familyName, foreName];
   };
   this.getName = function(){
      return this.name[0] + " " + this.name[1];
   };
}
var person1 = new Person("Csavardi", "Samu");
var person2 = new Person("Zsákos", "Frodó");

//VIP konstruktor
function VIP(familyName, foreName){
   this._proto_.constructor.call(this, familyName, foreName);
   this.status = 2;
}

//Kapcsolódás a Person prototípus láncához.
VIP.prototype = new Person();
VIP.prototype._proto_ = Person.prototype;

//VIP objektum létrehozása
var person3 = new VIP("Szürke", "Gandalf");
alert( person3.getName() );
A példakódban szereplő objektumok prototípus lánca:

Object.prototype
   |
   - Person.prototype
        |
        - person1
        |
        - person2
        |
        - VIP.prototype
            |
            - person3


Az ábra semmiben nem különbözik a prototípus lánc leírásánál szereplő ábrától. A lényeges különbség, ami nem látszik az ábrán, hogy a person3 számára a getName metódust nem a Person.prototype, hanem az VIP.prototype szolgáltatja. Ha valamiért a VIP.prototype mintadarabot sem akarjuk használni, akkor a konstruktorban kell átmásolnunk a Person.prototype objektum tulajdonságait. Ebben az esetben a person3 objektum nem kapcsolódna a Person.prototype prototípus lánchoz, így annak módosításai nem lennének hatással a létrehozott person3 objektumra.

Szabvány metódusok és tulajdonságok

Hasznos lehet néhány beépített tulajdonsággal és metódussal megismerkednünk, amik segítségül jöhetnek az objektumok kezelésekor. Az itt felsorolt tulajdonságok részét képezik az Object.prototype objektumnak, azaz minden egyéb objektum is rendelkezik ezekkel a tulajdonságokkal és metódusokkal. De bármelyik objektumhoz készíthetünk sajátot, amivel elfedhetjük az alapértelmezettet.

constructor

Az objektumok constructor tulajdonsága az adott objektum konstruktorát adja meg. Minden függvény prototype.constructor jellemzője automatikusan beállításra kerül, akkor amikor a függvény létrejön. A tulajdonsággal hasonló ellenőrzéseket tudunk elvégezni az objektumokon:

//Függvény a dátum típus ellenőrzéséhez
function isDate(d){
   return d && d.constructor && d.constructor == Date;
}

//Metódus az Object prototype bővítésével
Object.prototype.isDate = function(){
   return this.constructor == Date;
}
A függvény esetében első lépésben ellenőrizzük, hogy létezhet-e a paraméter constructor tulajdonsága, például primitív típusú változók esetén nincs. Majd megvizsgáljuk, hogy a constructor egyenlő-e a Date függvénnyel. Az Object prototype bővítésekor ezeket az ellenőrzéseket elhagyhatjuk.

Fontos megjegyezni, hogy ha az objektum leírásakor a prototype tulajdonságot objektum literállal adjuk meg, akkor fölülírjuk a konstruktor definiálásakor automatikusan meghatározott értéket, és a constructor az objektum literál konstruktorát, az Object konstruktort fogja visszaadni. Ekkor nekünk kell megadnunk a megfelelő értéket. Ezen kívül ugyanilyen felülírás történik a prototípus lánc továbbfűzésekor is.

//A constructor tulajdonság meghatározása.
Foo.prototype.constructor = Foo;

//Vagy objektum literállal.
Foo.prototype = {
   constructor : Foo

}

isPrototypeOf

A prototype objektumok isPrototypeOf metódusával el tudjuk dönteni, hogy a paraméterül átadott objektum hozzákapcsolódik-e az adott prototípus lánchoz, vagy sem. Hasonló metódusokat tudunk például készíteni:
Object.isHTMLElement = function(){
   //IE alatt nincs HTMLElement objektum, a metódus nem fog jól működni.
   return HTMLElement.prototype.isPrototypeOf(this);
}
A metódussal eldönthetjük, hogy az adott objektum, HTML elem-e vagy sem. Ha az objektum kapcsolódik a HTMLElement által indított prototípus lánchoz, akkor igaz értékkel tér vissza. A JavaScript alap objektumok, a weboldal szövegei, megjegyzései és a document objektum sem kapcsolódik a HTMLElement prototípus láncához.

hasOwnProperty

Az objektumok hasOwnProperty metódusa visszaadja, hogy a megadott tulajdonságnév az adott objektumhoz tartozik-e. Nem vizsgálja a prototípus láncot, csak magát az objektumot. Ha tudjuk, hogy az adott tulajdonság létezik a prototípus láncban, akkor a hasOwnProperty metódussal megvizsgálhatjuk, hogy az objektum elfedi-e a tulajdonságot, vagy a prototípus láncban fellelhetőt használja.
Object.isPrototypeProperty = function(propertyName){
   //Nem az aktuális objektum saját tulajdonsága és definiált tulajdonság.
   return !this.hasOwnProperty(propertyName) && this[propertyName] !== undefined;
}

valueOf

Amikor egy objektum kifejezésben szerepel, akkor implicit módon az objektum valueOf metódusa kerül meghívásra, hogy a JavaScript megkapja azt a primitív értéket, amit a kifejezés kiértékeléséhez használhat. Azon kívül, hogy az objektumainkhoz is definiálhatunk saját valueOf metódust, átírhatjuk a beépített objektumokét is. A példában felülírjuk a Date.prototype.valueOf metódusát. Ezután, ha kifejezésben használunk egy dátumot, akkor a saját metódusunk által visszaadott Julian date értékkel fog számolni a JavaScript.
Date.prototype.valueOf = function(){
   return this.getJD();
}

//Dátum objektumok előállítása
var d1 = new Date;
var d2 = new Date(d1.getFullYear(), 0, 1);

//Január elseje óta eltelt napok száma.
alert( d1 - d2 );
Érdekes dolgot érhetünk el a Number típusú objektumok valueOf metódusának módosításával. Bármilyen értéket is tartalmaz a Number objektum, a visszatérési értéke 5/2 lesz.
Number.prototype.valueOf = function(){
   return 5/2;
}

var n1 = 2;
var n2 = new Number(2);

//Kétszer kettő néha öt.
alert( "2×2 = " + (n1*n2) );

toString

Az objektumok toString metódusa mind működésében, mind használatában hasonlít a valueOf metódusra, de akkor kerül meghívásra, ha az objektum primitív értékére szövegként van szükség. A metódus kiírásnál vagy sztringek összefűzésénél kerül implicit módon meghívásra.

Date.prototype.toString = function(){
   return this.getFullYear() + "." + (this.getMonth()+1) + "." + this.getDate() + ".";
}

//Az aktuális dátum év.hónap.nap. formátumban.
alert( new Date );
A Date.prototype.toString metódus átírása után, kiíratásnál a dátum magyar formátumban fog megjelenni. De természetesen egyéb, számunkra kedvezőbb formátumot is kreálhatunk, és ahogy a valueOf metódus esetén, az objektumainkhoz is definiálhatunk saját toString metódust. További próba a Number objektummal:
Number.prototype.valueOf = function(e){
   return this.toFixed() / 2;
}
Number.prototype.toString = function(){
   return this / 2;
}

//Mitirki?
alert( new Number(3) );

Néhány fontosnak vélt megjegyzés

Objektumok, metódusok és a this

A JavaScript nyelvben – és ezen már nem is csodálkozunk – a this sem ugyan azt jelenti, mint a többi nyelvben. Az ok szintén az, hogy a nyelv feladata egy már kész kiszolgáló környezetben maga a kiszolgáló környezet módosítása. Normál objektumorientált programozási nyelvben a this az osztály definíciójában az aktuális objektum, az osztálypéldány azonosítására szolgál. Mivel a JavaScriptben sem osztály, sem osztálypéldány sincs, így a this sem alkalmas az osztálypéldány azonosítására.

A this objektumot jelöl, de az értéke az aktuális futási környezetet jelenti. A this értéke attól függ, hogy az éppen futó kódot milyen környezetben hívtuk meg. A konstruktorban a new operátor használata miatt a this az előállítandó objektumnak felel meg. De egyáltalán nem biztos, hogy a metódusban a metódus objektumával egyenlő. A this értékét a metódusban is a hívási környezet fogja meghatározni. A Date.prototype bővítésénél biztosra mehettünk, mert a getJD metódust a következő módon hívhatjuk meg:
var d = new Date;
d.getJD();
Azaz a pont (.) operátorral meghatároztuk a hívási környezetet. Az minden esetben a d változó által megadott Date objektum. Ha a változó nem Date típusú, akkor nincs getJD metódusa sem, és nem kerül végrehajtásra.

Nézzünk egy másik példát, amihez hasonlókkal könnyen találkozhatunk. A weboldalakon elég gyakran előfordul, hogy egy területre kattintva egy másik terület tartalmát szeretnénk megjeleníteni vagy elrejteni. Természetesen létrehozhatunk függvényt is erre a feladatra, de sokkal hajlékonyabb megoldáshoz jutunk, ha eddigi ismereteinkre támaszkodva készítünk egy objektumot, ami összekapcsolja az egyik objektum eseményét a másik objektumon végzendő művelettel.
displayChanger.prototype = {
   //Jelenleg egy metódus tartozik az objektumhoz, amelyik elrejti-megjeleníti a tartalmat.
   changeDisplay : function(){
      this.content.style.display = this.content.style.display == 'none'? 'block':'none';
   }
}

//A konstruktor két paramétert vár. Azt, amire kattinthatunk label-nek neveztük el,
//az elrejtendő/megjelenítendő tartalom neve pedig content lett.
function displayChanger(label, content){
   //Eltároljuk a címke és a tartalom objektumokat.
   this.label      = document.getElementById(label);
   this.content    = document.getElementById(content);

   //Végül azt mondjuk a konstruktorban, hogy ha kattintanánk a label elemen, akkor hajtsuk végre a changeDisplay metódust.
   this.label.onclick = this.changeDisplay;
}

//Az előbb leírt objektumtípus használata, a címkék és a tartalom összerendelése.
new displayChanger("picture1", "menu1");
new displayChanger("picture2", "menu2");
A kód lefutása után azt hihetnénk, hogy mindent jól csináltunk, hisz ránézésre teljesen jól kell működnie ennek a nem túl bonyolult, összekötést megvalósító objektumnak. A konstruktor rendben le is fut, és beállítja a tulajdonságokat. De amint odáig merészkedünk, hogy kattintunk az egyik képen, akkor legjobb esetben is hibát fogunk visszakapni.

Az ok pedig az, hogy a changeDisplay metódus futásakor a this nem a displayChanger típusú objektumot fogja jelenteni, hanem a hívó felet. Azt, amelyikre éppen kattintottunk. Amikor értéket adtunk az onclick tulajdonságnak, akkor a hivatkozott metódus, mint függvény másolódik be a tulajdonság értékeként, és a click eseményre az a függvény fog végrehajtódni. Az egyik szemünk sír, a másik meg nevet. Mert bár a metódusban így nem kell találgatni, hogy melyik elemre kattintottunk, mivel a this ezt adja meg, de nem tudjuk megmondani, hogy melyik objektumnak kellene megváltoztatni a megjelenését. Hibajelzést kapunk, mert a picture1 és a picture2 elemeknek nincs content tulajdonsága.

Az egyik megoldás az, hogy a konstruktorban készítünk egy burkoló függvényt, ami – a változók láthatóságának kihasználásával – megoldja, hogy a changeDisplay metódusban a this a létrehozott displayChanger objektumot jelentse.
function displayChanger(label, content){
   this.label      = document.getElementById(label);
   this.content    = document.getElementById(content);

   this.label.onclick = changeWrapper(this);
   function changeWrapper(obj){
      return function(){ obj.changeDisplay() }
   }
}
A changeWrapper függvény egy nem nevesített függvénnyel tér vissza. A paraméterül kapott obj objektumot a konstruktor határozza meg, aminek az értéke az aktuálisan létrehozandó objektum lesz. A HTML elemen való kattintáskor ez a nem nevesített függvény fog végrehajtódni, ami semmi mást nem csinál, csak meghívja a konstruktor által megadott objektum changeDisplay metódusát. Ekkor a metódusban a this a nekünk megfelelő objektumot fogja jelenteni. A burkoló függvény használatával biztosíthatjuk a this megfelelő értékét, és paramétereket is átadhatunk a metódusnak.

Természetesen a problémára egyéb megoldás is adódhat. Nem csak ilyenféle burkoló függvények használatával oldható meg ez a jelenség. Ezzel a megoldással a saját elképzelésünket erőltetjük a JavaScriptre. Egy másik megoldás lehet az, hogyha a kattintás pillanatában ismerjük a hívó elemet, akkor arra az elemre aggatjuk a többi tulajdonságot, és a kattintáskor lefutó függvény olvassa ki onnan, hogy melyik elemet kell megváltoztatni.

Tanulmányozáshoz itt egy bővebb változata az objektumnak, ami a megadott sütibe elmenti az állapotot, és az objektumok létrehozásakor visszaállítja a felhasználó számára az oldal legutóbbi kinézetét.
function changeContent(label, content, cookieName){
   this.label      = document.getElementById(label);
   this.content    = document.getElementById(content);
   this.cookieName = cookieName;

   addEvent(this.label, 'click', changeWrapper(this));
   function changeWrapper(obj){ return function(){ obj.change() }}

   if (this.getSavedStatus() == this.SHOW){ this.changeDisplay() }
}

changeContent.prototype = {
   HIDE : -1,
   SHOW : 1,

   change : function(){
      this.changeDisplay();
      this.saveStatus();
   },

   changeDisplay : function(){
      try {
         this.content.style.display = this.content.style.display=='none'? 'block':'none';
      }catch(er){}
   },

   saveStatus : function(){
      if (!this.cookieName){ return; }

      var contentStatus = this.content.style.display=='none'? this.HIDE:this.SHOW;
      setCookie(this.cookieName, contentStatus, 31536000000);
   },

   getSavedStatus : function(){
      if (!this.cookieName){ return; }
      return parseInt(getCookie(this.cookieName))==this.HIDE? this.HIDE:this.SHOW;
   }
}

Objektumok, függvények és a statikus változók

A függvényekben definiált változók élettartama a változó definiálásától a függvény futásáig tart. Bár a JavaScript nyelvben a static foglalt szó, ennek ellenére nem tudunk statikus változót definiálni, ami két függvényhívás között is megtartaná az értékét. Megoldás lehet, hogy globális változót használunk, ez egészen addig tűnik jó megoldásnak, amíg nem kezdenek szaporodni a függvényeink, és fölül nem írjuk az egyik függvényben egy másik függvény globális változóját.

A statikus változóra a választ az objektumok megismerése adja. Tudjuk azt, hogy maguk a függvények is változók, Function típusú objektumok. Az objektumokhoz pedig bármilyen tulajdonságot definiálhatunk, aminek az élettartama a definiálástól a függvény létezéséig tart. Így a függvény-objektum meg fogja védeni a „statikus változónkat” a véletlen változtatástól, és az értékét is megőrzi.

Mire használhatunk statikus változókat? Például eredmények mentésére, azaz gyorsítótárazásra, ami a JavaScript esetén nem jön rosszul. A következő metódus erre mutat egy példát:
//Adott osztályú elemek keresése, a tagName paraméterek megadása nem kötelező.
document.getElementsByClassName = function(className, tagName){
   //Hivatkozás az aktuális metódusra.
   var method = this.getElementsByClassName;

   //Ha az aktuálisan keresett osztálynév megegyezik az előzővel, nem hajtjuk végre a keresést.
   if (!(method.prevClassName === className && method.prevTagName === tagName && method.found)){
      //A tulajdonságok definiálása.
      method.prevClassName = className;
      method.prevTagName = tagName;
      method.found = [];

      //A HTML elemek kinyerése, és a megfelelő RegExp objektum előállítása.
      var
      elements = this.body.getElementsByTagName(tagName? tagName:"*"), element,
      rx = new RegExp('\\b' + className.replace(/\-/g, '\\-') + '\\b');

      //Végiglépkedünk az elemeken, a megfelelő elem hozzáadása a gyorsítótárhoz.
      for (var i=0; i < elements.length; ++i){
         element = elements[i];
         if (element.className && rx.test(element.className)){
            method.found.push(element);
         }
      }
   }
   //Visszatérünk a talált elemekkel, amiket a metódus a következő hívásig megjegyez.
   return method.found;
}
A getElementsByClassName metódus megjegyzi az előzőleg talált elemeket, így nem kell végiglépkedni a weboldal összes elemén, ha újra ugyanahhoz az osztályhoz tartozó elemeket keressük. A második hívásra az eredményt majdhogynem azonnal megkapjuk. Jelentős teljesítménynövekedést érhetünk el a gyorsítótárazás megoldásával. Ezen kívül már könnyen megoldhatjuk, hogy következő kérésnél a találatokat fűzze hozzá az előzőhöz. Statikus változók a JavaScriptben nincsenek; csak objektum tulajdonságokat használunk úgy, mintha statikus változók lennének.

Objektumok, sztringek és a JSON

AJAX technika alkalmazásakor különféle típusú adatokat szeretnénk mind küldeni, mind fogadni, de az adatokat szöveges formátumba kell öltöztetni, hogy továbbítani tudjuk. Erre nyújt egy megoldást a JSON leírás. Amit – most már ismerve a JavaScript objektumkezelését – könnyen megérthetünk. Az elnevezés is erre utal: JavaScript Object Notation ([i]JavaScript Objektum Jelölés).

A JSON leírás azt mondja, hogy az elküldendő adatokat írjuk le a JavaScriptben használatos objektum vagy tömb literállal. Majd ezt a literált küldjük el szövegként, amit majd a fogadó értelmezni fog. Annyi megszorítást tesz, hogy objektumok esetén a név-érték pároknál a nevet kettős idézőjelek (") közzé kell tenni, az értékek pedig a következők lehetnek: kettős idézőjelekkel határolt sztring, szám, true, false vagy null érték, illetve további ennek a leírásnak megfelelő objektum vagy tömb literál. Ezeket a megszorításokat azért alkalmazza, hogy a fogadó fél a sztring értelmezése után biztosan azt az objektumot vagy tömböt kapja meg, amit elküldtünk.
//Az előállított objektum szerkezete.
{
   first_item  : '5',
   second_item : undefined,
   third_item  : [5, 7, 37, 52]
}

//A továbbítandó szöveg, ami az objektum literált tartalmazza.
'{"first_item":"5","second_item":null,"third_item":[5,7,37,52]}'
A cikk elején számos ilyen objektum literállal ismerkedhettünk meg. A JSON leírás azt mondja, hogy küldjük el az ilyen adathalmazt szövegként. A veszélyt éppen ez jelentheti, mivel azért, hogy megkapjuk a JavaScript jelölésrendszer szerint összeállított adatokat, a szöveget általában valamiféle eval függvénnyel értelmeznünk kell. Azaz a fogadó oldalon végre kell hajtani egy ismeretlen programkódot. Ami jó esetben csak egy objektum literált tartalmaz, rossz esetben nem. A JSON leírás a következő metódust adja a szöveg ellenőrzéséhez:
String.prototype.parseJSON = function(){
   try {
      return
         !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
           this.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + this + ')');
   }catch(e) {
      return false;
   }
}
A metódus első lépésben kiszedi a kapott szövegből a kettős idézőjelek (") között lévő szövegrészeket, majd a megmaradt karaktereket ellenőrzi. Ezek a karakterek a sztringen kívül engedélyezett értékekből adódnak: true, false, null, objektum literál: {}, tömb literál: [], számok jelölése és elválasztójelek. Betörő legyen a talpán, aki ezekből a megmaradt karakterekből értelmes kódot akar előállítani. Mindenesetre a föntihez hasonló ellenőrzést a szövegben lévő adatszerkezet előállítása előtt alkalmaznunk kell szerver és kliens oldalon is.

Összefoglalás

Ezzel véget ért a JavaScript objektumkezelésével való ismerkedésünk. Remélem, sokak hasznos leírásnak fogják találni, és Felhő cikkével együtt többen megfelelő alapot szerezhetnek a JavaScript objektumorientált programozásához. Akár a nulláról az ötödik vagy hetedik szintre :) tudnak eljutni. De a cikk végére, ha böcsületesen elolvastad, a következő kérdésekre mindenképpen meg tudod adni a helyes választ:

  1. Mi ez a sok hülye zárójel?
    A kapcsos zárójel ({}) nem csak blokk és utasítások zárójelezésre, hanem objektum literál jelölésére is használatos.
  2. Mi az a prototípus szerinti objektumkezelés?
    A prototípus szerinti objektumkezelés alapja az, hogy minden objektum egy belső hivatkozást tartalmaz a mintadarabjára.
  3. Hogyan lehet osztályt létrehozni?
    Nem lehet létrehozni, mert nincs. Egy objektum jellemzőit vagy a konstruktor határozza meg, vagy a prototípus láncban látható objektumok tartalmazzák. Az objektum leírása nincs elkülönítve az objektumtól. Minden objektum egy Global típusú objektum része.
  4. Hogyan lehet objektumot létrehozni?
    Objektum literállal, vagy new operátorral beépített vagy saját konstruktor hívásával.
  5. Hogyan lehet tulajdonságot definiálni?
    Mivel a JavaScript nem típusos nyelv, ezért egy tulajdonság létrehozásához elegendő értéket adnunk a tulajdonságnak. Többféle módon is definiálhatunk tulajdonságot, például a konstruktorban a pont (.) operátorral. A prototípus láncban az adott objektum felől látható részen bárhol definiált tulajdonság az objektum része lesz.
  6. Hogyan lehet metódust definiálni?
    Ugyanúgy, ahogy tulajdonságot. A metódusok Function típusú tulajdonságok.
  7. Mire való a prototype tulajdonság?
    A tulajdonságok megosztására és az örökélés megvalósítására. Ez utóbbi is az előzővel valósul meg.

A cikkben szereplő kódokat letöltheted, ha nem szeretnél foglalkozni a gépelésükkel.
 
1

még még még ennyi nem elég :)

toxin · 2007. Ápr. 30. (H), 21.04
jó lenne még :) , az elméleti után egy gyakorlattal foglalkozzok cikkek sora is (sicc :))) (tippek-trükkök, példák minden mennyiségben) pl. ilyenekre gondolok, hogy poliformizmus megvalósítása (polymorphism), konstruktor láncolás (constructor chaining), kölcsönvett metódusokkal megvalósított öröklődés (borrowing methods, mixin classes vagy mixins), különböző keretrendszerek által megvalósított, alternatív OOP megoldások ( prototype, base stb.) , privát és privilegizált tulajdonságok metódusok stb. (most ezek juttottak eszembe)

Első kettőre egy gyors példa :)

function FooA(a){
	this.a = a;
}

FooA.prototype.add = function(){
	return this.a += 1;
}
// prototype láncolása
FooB.prototype = new FooA;
// konstruktor átírása
FooB.prototype.constructor = FooB;
// ősosztály beállítás
FooB.prototype.superClass = FooA;
// szükségtelen öröklődött tulajdonságok törlése
delete FooB.prototype.a;
function FooB(a,b){
	// konstruktor láncolása
	this.superClass(a);
	this.b = b;
}
FooB.prototype.add = function(){
	return this.a += 10;
}

fooB_1 = new FooB(1,2);
// példa a poliformizmusra, és a felülirt metódus hívásra
if (window.console)console.log(fooB_1.a,fooB_1.b,fooB_1.add(),FooA.prototype.add.apply(fooB_1));
üdv t

ui: természetesen, részemről nagyon nagy köszönet a fenti cikkért
2

Re: még még még

Balogh Tibor · 2007. Május. 1. (K), 09.03
jó lenne még :) , az elméleti után egy gyakorlattal foglalkozzok cikkek sora is

A Weblabor szerkesztői szívesen veszik Tőled is a cikkeket. Tessék megírni, és lesz! A példádból látszik, hogy számos dolgot meg lehet még oldani a JavaScriptben. Ahogy fölvetetted, érdemes lehet erről is írni.

természetesen, részemről nagyon nagy köszönet a fenti cikkért

Akkor érdemes volt elolvasni?
3

Douglas Crockford — "Advanced JavaScript"

toxin · 2007. Május. 1. (K), 10.29
természetesen érdemes, én még csak ifjú padawan vagyok a témában, cikkek talán később :) addig is akit érdekel a téma, üljön be Yahoo! mozijába és tekintse meg a következő előadást

Douglas Crockford — "Advanced JavaScript"
http://developer.yahoo.com/yui/theater/ (2. oszloban)

üdv t
7

Douglas Crockford előadások

Balogh Tibor · 2007. Május. 3. (Cs), 13.42
Sajnálom, hogy ezeket az előadásokat csak most láthattam. Nem kellett volna szenvednem az ECMAScript specifikáció böngészésével. A specifikációk, és erről Felhő minden bizonnyal meg tud erősíteni, egyébként sem tartoznak a legszórakoztatóbb olvasmányok közzé.

De az előadásokat nem is láthattam volna, a cikk még tavaly ősszel készült, csak a weblaborosok most jutottak odáig, hogy publikálni tudták, az előadások meg 2007. januárjában hangoztak el. Az első két előadást megnézve, mindenkinek csak ajánlani tudom, hogy tekintse meg őket, 0-tól 99 éves korig. Az meg csak engem nyugtat meg, hogy Crockford előadása hasonlóan épül fel, mint a cikkem. :) - Ezzel nem azt akarom mondani, hogy bátorkodnám hozzá mérni magam. Csupán azt, hogy valószínűleg hasonlóan gondolkodunk a programozási nyelvekről.

Köszönet a linkért! Sokaknak hasznos "mozi" lesz!
19

Én láttam…

presidento · 2010. Május. 12. (Sze), 23.54
…de így is nagyon jó olvasmány volt a cikked. Köszi szépen, hogy ilyen alaposan összefoglaltad!
6

Szép

vbence · 2007. Május. 2. (Sze), 11.40
Csatlakozom, a cikk nagyon színvonalas, élvezetes. A konstruktor láncolást nagyon hiányoltam belőle, enélkül a gyakorlatban nehezen boldogul az ember, egy plusz bekezdést megérne, és sokkal teljesebb lenne a kép vele. Meg persze kíváncsi volnék, hogy egy harcedzett szkriptguru hogyan csinálja.

Ha már volt postolt kód, akkor had küldjek be én is egy (nem túl szép) megoldást:

Szemely = function (nev, cim) {
    this.nev = nev;
    this.cim = cim;
}

Ugyfel = function (nev, cim, telefon, status) {
    this.constructor.prototype.constructor.call(this, nev, cim);    // Itt hívjuk meg a (kvázi) szülő kontruktorát
    this.telefon = telefon;
    this.status = status;
}

Ugyfel.prototype = new Szemely();
Annyi előnye van, hogy csak egy sor, és nem igényel plusz aparátust.
8

Köszi!

Balogh Tibor · 2007. Május. 3. (Cs), 13.57
Amit írsz, az már egy másik témakört nyit meg, ahogy ezt toxin megjegyezte. A teljesebb képhez egy harmadik cikket kellene írni. - Az igazán teljes képhez el kellene magyarázni az oop fogalmakat, majd tárgyalni kellene ezeket a JavaScript oldaláról. - Csak biztatni tudlak Téged is!

Meg persze kíváncsi volnék, hogy egy harcedzett szkriptguru hogyan csinálja.
Ez szerintem Felhő vagy András lesz!
9

this.constructor

Balogh Tibor · 2007. Május. 5. (Szo), 10.16
Ezzel a kóddal több gond is van:

this.constructor.prototype.constructor.call(this, nev, cim);
Felesleges ismétlést tartalmaz. Először lekéred implicit, majd explicit módon a constructor tulajdonságot. Elég így írni, és ekkor már feltűnik, hogy miért furcsa, hogy működik a konstruktorban, amit írtál:

this.constructor.call(this, nev, cim);
Mivel a tulajdonságnak a konstruktort kellene visszaadnia, így a konstruktorban végrehajtva azt csinálná a kód, hogy állandóan meghívná magát a konstruktor. Hogy miért nem ez történik, és miért a Szemely függvényt tartalmazza a tulajdonság, azt nem tudnám megmondani.

Adj értéket a Szemely prototype tulajdonságnak. Mindjárt nem lesz jó, amit írtál.

Szemely.prototype = {
   élőlény : true
}
Például Felhő cikkében szerepel a __proto__ tulajdonság, ami a mintadarabra való hivatkozást jelenti. Így azzal lehet mozilla-féle böngészőkben a mintadarabra mutatni.

this.__proto__.__proto__.constructor.call(this, nev, cim);
Vagy úgy ahogy Toxin által írt példa mutatja, megjegyzed, hogy kitől van származtatva az objektum, így meghívhatod a konstruktorban.
10

Kiigazítás

Balogh Tibor · 2007. Május. 6. (V), 09.01

function Szemely(nev, cim){
  this.nev = nev;
  this.cim = cim;
}
Szemely.prototype.toString = function(){
  return this.nev.toString();
}
function Ugyfel(nev, cim, statusz){
  //A Szemely konstruktor, nem az Ugyfel!
  this.constructor(nev, cim);
  this.statusz = statusz;
}
Ugyfel.prototype = new Szemely();
function Vip(nev, cim){
  //A Szemely konstruktor, nem az Ugyfel!
  this.constructor(nev, cim);
  this.statusz = null;
}
Vip.prototype = new Ugyfel();
A Szemely prototype.constructor tulajdonsága a Szemely konstruktort tartalmazza, de objektum literál használatakor fölülírjuk a beállított értéket, így az az Object konstruktor lesz.

Ugyan ez történik a prototípus lánc továbbfűzésekor. Az ebből leszármaztatott objektumok mind ugyanazt fogják tartalmazni, ahogy literál használatakor felűlírásra kerül az eredeti érték.
4

gratula!

virág · 2007. Május. 1. (K), 12.05
Szia,

szuper cikk, én nem vagyok túlságosan jártas JS-ben, de ebből nagyon sokat tanultam. Köszi a rászánt időt és a publikációt.

Minden jót!
5

Köszi!

Balogh Tibor · 2007. Május. 1. (K), 12.29
A publikáció természetesen a Weblabor szerkesztőket dicséri! Megszenvedtek vele ők is!

Akkor az eddigi hozzászólásból levonhatom, hogy a bevezetőben kitűzött célt sikerült elérni.
11

grat;)

SamY · 2007. Május. 20. (V), 19.24
Gratulálnék Én is a cikkhez! Már kostolgattam én is ezt-azt így JS objektumok terén, amit sikerült a prototype.js -ből ellesnem szintaktikát, de így legalább már világos, hogy mit miért is csináltam:)

Üdv,
SamY
14

:)))

Balogh Tibor · 2007. Május. 25. (P), 11.27
de így legalább már világos, hogy mit miért is csináltam:)

Kösz! És örülök, hogy segített a cikk!
12

Globális-Lokális változók

inf · 2007. Május. 22. (K), 12.08
igazából az előző cikkhez is tartozik a kérdésem:

van mondjuk egy ilyen kód:


function Class(constructor,prototype){
 var global={};
 var new_class=function ()
 {
  var local={};
  arguments.callee.prototype.setLocal=function (attribute,value){local[attribute]=value};
  arguments.callee.prototype.getLocal=function (attribute){return local[attribute]};
  constructor.call(this);
 }
 new_class.prototype=prototype;
 new_class.prototype.setGlobal=function (attribute,value){global[attribtue]=value};
 new_class.prototype.getGlobal=function (attribute){return global[attribute]};
 return new_class;
}

var obi=new Class(
 function ()
 {

 },
 {
  
 }
);

var a=new obi();
var b=new obi();
var c=new obi();

a.setLocal("x",30);
b.setLocal("x",40);

alert(a.getLocal("x")+","+b.getLocal("x")+","+c.getLocal("x"));

ilyenkor hova kerül a local nevű object? ha arguments.callee.prototype helyett this-t használok természetesen működik a setLocal és getLocal úgy ahogy kell, viszont ha így állítom be a függvényeket, akkor lokális változóból valahogy globális változó lesz :)
és igazából ez a legnagyobb hibája az egész prototypeos módszernek, ezért nem szívesen használom saját class-eknél, mert a lokális változókat nem tudja kezelni sehogy sem.

szóval mi történik ilyenkor a local-lal???

(ami fura az egészben, hogy ha globális változót kérne le ilyenkor a prototypeba rakott függvény, akkor hibaüzenet jönne, viszont a localnak minden létrehozott object esetén másnak kéne lennie, szóval ez így alapból teljesen logikátlan nekem)
13

megoldás

inf · 2007. Május. 22. (K), 15.11
közben rájöttem a megoldásra:
-elsőre létrehozza a prototypeot a konstruktorral, és a private változókat is deklarálja ugyanúgy, mint a publicokat a prototypeban.
-utána ha bmilyen módosítás történik, akkor azt már a létrehozott objecten hajtja végre, és nem a prototypeban, tehát ha mondjuk egy private változót módosítunk egy a konstruktorban elhelyezett this-es fgvel, akkor létrejön egy private változó (feltéve ha létezett a prototypeban is olyan private változó (ha nem, akkor globalnak veszi a javascript, mert ugyanolyan szintaxissal hivatkozunk rá), majd ennek az értéke változik. ha viszont a prototype egy függvényéből módosítjuk az adott változót, akkor a prototypeban módosul a private típusú változó, és az összes objectnek, aminek nem lett vmilyen konstruktoros függvénnyel megváltoztatva az adott változója módosul az érték..

tehát a példában a local-lal az történik, hogy a prototypeból szedi le a prototype private változóját és nem az adott object private változóját kéri le.
nah most szerintem ez így elég nagy bugnak számít, mert ha megnézzük a public változókat, akkor azokat a prototypeban megadott függvények az adott objecten módosítják, és nem a prototypeban

adok kódot, amivel végülis teszteltem:
<html>
<head>
<style>
table
{
 background-color: yellow;
}

td
{
 background-color: gray;
}

div
{
 background-color: gray;
 border: 2px solid yellow;
}
</style>
<script>
function Class(constructor,prototype){
 var global={};
 var new_class=function ()
 {
  var local={x:"default x",y:"default y",z:"default z"};
  this.setPrivate=function (attribute,value){local[attribute]=value}
  this.getPrivate=function (attribute){return local[attribute]} 
  arguments.callee.prototype.setLocal=function (attribute,value){local[attribute]=value}
  arguments.callee.prototype.getLocal=function (attribute){return local[attribute]} 
  constructor.call(this);
 }
 new_class.prototype=prototype
 new_class.prototype.setGlobal=function (attribute,value){global[attribtue]=value}
 new_class.prototype.getGlobal=function (attribute){return global[attribute]} 
 return new_class
}

var obi=new Class(
 function ()
 {

 },
 {
  
 }
)


function init()
{
 var a=new obi();
 var b=new obi();

 a.setLocal("x","a.x prototype")
 b.setLocal("y","b.y prototype")
 a.setPrivate("x","a.x constructor")
 b.setPrivate("y","b.y constructor")
 a.setLocal("z","a.z constructor")

 var table=document.getElementsByTagName("TBODY")[0]
 table.addRow=function (title,local,private)
 {
  var t=document.createElement("TD");
  t.innerHTML=title;
  var l=document.createElement("TD");
  l.innerHTML=local;
  var p=document.createElement("TD");
  p.innerHTML=private;
  var tr=document.createElement("TR");
  tr.appendChild(t);
  tr.appendChild(l);
  tr.appendChild(p);
  this.appendChild(tr);
 }

 table.addRow("x in a",a.getLocal("x"),a.getPrivate("x"));
 table.addRow("y in a",a.getLocal("y"),a.getPrivate("y"));
 table.addRow("z in a",a.getLocal("z"),a.getPrivate("z"));
 table.addRow("x in b",b.getLocal("x"),b.getPrivate("x"));
 table.addRow("y in b",b.getLocal("y"),b.getPrivate("y"));
 table.addRow("z in b",b.getLocal("z"),b.getPrivate("z"));

}
</script>

</head>
<body onload="init()">
<table cols="3">
<thead>
 <tr><td colspan="3">Private Vars</td></tr>
 <tr><td>var in object</td><td>prototype</td><td>constructor</td></tr>
</thead>
<tbody>
</tbody>
</table>
</body>
</html>
nah most ezt nem tudom, hogy bugnak tekintsem, vagy direkt írták meg így a jst, hogy módosítani lehessen a private változók alapbeállítását is.. viszont így választhat az ember, hogy spórol a memóriával, vagy oopzik.
15

Változók láthatósága és élettartama

Balogh Tibor · 2007. Május. 25. (P), 17.25
Inf3rno!

szóval mi történik ilyenkor a local-lal???

Egy függvényblokkon belül a változók láthatósága a függvényblokkra korlátozódik. De minden, az adott blokkon belül definiált függvény látni fogja a blokk változóit. Mivel további hivatkozás is van a változóra, ezért az továbbra is élni fog, de csak az adott függvényeken belül látható. - (Megjegyzés: A JavaScript csak függvényblokkot ismer, ellentétben a C-vel. Az utasításoknál használható kapcsos zárójelek utasítás zárójelek, nem blokkok, ahogy a Pascalban a begin...end páros.)

A Class függvény minden esetben egy függvénnyel tér vissza, ezért a hívásakor a new használata felesleges. A Class futásakor a new_class változó csak értéket kap, de nem fut le, hisz nem kerül meghívásra.

A new_class függvényben a arguments.callee.prototype a new_class.prototype kifejezésnek felel meg. Azért kell a new_class függvényben definiálni a getLocal és setLocal függvényeket, hogy láthassák a konstruktorban megadott local változót.

A new_class konstruktor hívásakor a new_class.prototype.setLocal metódus szintjén is látható lesz a local változó, de minden egyes konstruktor híváskor az felülíródik, mivel a változónevek egyediek. Ez a felülírás a this.setLocal használatával értelemszerűen nem történik meg.

Remélem, érthető volt! Nem néztem pontosan utánna a dolgoknak, ha valahol tévedtem Felhő minden bizonnyal ki fogja egészíteni a leírtakat.

Azt hittem érthető volt a cikkből, hogy a szokásos OOP ráerőltetése a JavaScriptre felesleges. Megvan a JavaScript saját, prototípus szerinti objektumkezelése.
16

OK

inf · 2007. Május. 31. (Cs), 02.35
Rendben, tudomásul vettem :)
Viszont van másik kérdésem, ami kicsit egyszerűbb:

var c1=function (){arguments.callee.prototype={a:20}}
var c2=function (){arguments.callee.prototype.a=20}

var x1=new c1();
var x2=new c2();
alert(x1.c+","+x2.a);
na mármost az első esetben "a" értéke undefined lesz, a második esetben pedig húsz. miért?

jah, másik érdekesség, most találtam, firefox core javascript bug:


function TEST()
{
 var prototype={}

 function BUG(data)
 {
  this.builder=function ()
  {
   this.data=data;
  }
  this.builder.prototype=prototype;
 }

 this.test=function (){alert("Is it bug?\n"+!(new BUG(100) instanceof BUG))}
}

var a=new TEST()
a.test();
17

Konstruktor és a prototípus lánc

Balogh Tibor · 2007. Jún. 3. (V), 11.44
na mármost az első esetben "a" értéke undefined lesz, a második esetben pedig húsz. miért?

Bocs, hogy kicsit későn válaszolok, most jutott rá időm. Egyébként is az ilyen kérdéseket talán Felhőhöz kellene intézned! :) Azért undefined mert a prototípus lánc nem a konstruktorban felülírt prototype tulajdonságra fog mutatni. Tanulság: ne írd fölül a prototípust a konstruktorban!

   x1.constructor.prototype !== x1.__proto__
   x2.constructor.prototype === x2.__proto__
18

Kösz

inf · 2007. Jún. 5. (K), 12.45
Köszi a válaszokat. :-)
20

+3 adattípus?

Horváth Norbert · 2014. Nov. 19. (Sze), 15.51
Ahogy a fönti felsorolásból látható, a JavaScript hatféle alap adattípust ismer. Illetve van még plusz három, de számunkra most nem lényegesek, mivel azokat csak belső tárolásra használja a nyelv.


Még régen olvastam a cikket, és ez adta a lökést hogy megismerjem a js oop "oldalát", de azóta se tudom mi ez a további 3 adattípus. Valaki le tudná írni a nevüket hogy rá tudjak keresni?
21

Jó kérdés

Poetro · 2014. Nov. 19. (Sze), 18.17
Jó kérdés, hogy mi lehetett az a 3, ugyanis azóta jó pár tucat újabb született az ES6-ben. Valószínűleg a Date, Error, RegExp típusokra gondolt, ugyanis ezek léteztek ES3-ban is.
22

Én valahogy úgy értettem hogy

Horváth Norbert · 2014. Nov. 19. (Sze), 22.18
Én valahogy úgy értettem hogy a primitív típusokra + object vonatkozik ez a mondat, tehát a Number, String, Boolean, Undefined, Null, Object listára, hogy ezek mellett van még +3. De lehet rosszul értelmezem.