Thomas: Objekthierarchien selbst definieren

Hi JavaScript-Experten!

Ich würde gerne Objekthierarchien definieren; aber was beispielsweise mit VB ein Kinderspiel ist, will mir mit JavaScript nicht so recht gelingen. Das Problem sehe ich in den Arrays.

Beispiel:

function Book(Titel, Autor) {
 this.Titel = Titel;
 this.Autor = Autor;
 this.Kapitel = new Array();
}

function Chapter(Ueberschrift) {
 this.Ueberschrift = Ueberschrift;
}

var Buch = new Array();
Buch[12] = new Book("SELFHTML", "Stefan Münz");
Buch[12].Kapitel[2] = new Chapter();
Buch[12].Kapitel[2].Ueberschrift = "HTML";
Buch[12].Kapitel[5] = new Chapter();
Buch[12].Kapitel[5].Ueberschrift = "JavaScript";

Dieser Code funktioniert zwar; bei einer umfangreicheren Objekthierarchie finde ich die ewige Instanzierung (z.B. = new Chapter()) doch lästig.
Kann man das vielleicht auch im Konstruktor unterbringen? In VB würde ich Collection-Objekte erzeugen; also z.B. Chapters vom Typ Chapter.

Mein *Wunsch*code würde z.B. so aussehen:

var MyBooks = new Books();
MyBooks[12].Titel = "SELFHTML";
MyBooks[12].Autor = "Stefan Münz";
MyBooks[12].Chapters[2].Ueberschrift="HTML";
MyBooks[12].Chapters[5].Ueberschrift="JavaScript";

Möglich?
Wie müßten die Konstruktoren dafür aussehen??

Für Eure Antworten bin ich dankbar; auch für den Fall, dass sie "in JavaScript generell NICHT möglich" lautet!

Thomas

  1. Grüssi,

    Ich würde gerne Objekthierarchien definieren; aber was beispielsweise mit VB ein Kinderspiel ist, will mir mit JavaScript nicht so recht gelingen. Das Problem sehe ich in den Arrays.

    Beispiel:

    function Book(Titel, Autor) {
    this.Titel = Titel;
    this.Autor = Autor;
    this.Kapitel = new Array();
    }

    function Chapter(Ueberschrift) {
    this.Ueberschrift = Ueberschrift;
    }

    var Buch = new Array();
    Buch[12] = new Book("SELFHTML", "Stefan Münz");
    Buch[12].Kapitel[2] = new Chapter();
    Buch[12].Kapitel[2].Ueberschrift = "HTML";
    Buch[12].Kapitel[5] = new Chapter();
    Buch[12].Kapitel[5].Ueberschrift = "JavaScript";

    Dieser Code funktioniert zwar; bei einer umfangreicheren Objekthierarchie finde ich die ewige Instanzierung (z.B. = new Chapter()) doch lästig.

    äähhm, meintest du vielleicht so etwas in diese Richtung:

    // Konstruktor für das Buch
    function Book(Titel, Autor) {
      this.Titel = Titel;
      this.Autor = Autor;
      this.Kapitel = new Array();
    }

    // "Pseudo-Konstruktor" für die Kapitel
    function addChapter(Index, Ueberschrift) {
      this.Kapitel[Index] = Ueberschrift;
    }

    // Und auch ne Methode zum ausgeben:
    function getChapter (Index) {
      return this.Kapitel[Index];
    }

    // Die Funktion, zu einer Instanzmethode machen
    Book.prototype.addChapter = addChapter;
    Book.prototype.getChapter = getChapter;

    // Aufruf:
    b = new Book("SELFHTML","Stefan Münz");
    b.addChapter(1,"Editorial");
    b.addChapter(2,"Einführung");

    // Test:
    alert(b.getChapter(1));
    alert(b.getChapter(2));

    Es wär zwar schöner ein Array von Chapter-Objekten im Book-Konstruktor anzulegen, es fällt mir aber auf die schnelle nicht wirklich was dazu ein ;-)

    hth,
       regenfeld

    1. Noch ne kleine Änderung:

      function addChapter(index, ueberschrift, start_seite) {
        this.Kapitel[index] = new Chapter(ueberschrift,start_seite);
      }

      function Chapter (ueberschrift,start_seite) {
        this.ueberschrift=ueberschrift;
        this.start_seite=start_seite;
      }

      wäre schöner, denn dann hast wirklich ein eigenes Objekt "Chapter", das dann auch mehr als nur eine Variable haben kann. Somit wird auch der "Aufruf" so wie du ihn anfangs haben wolltest:

      b.Kapitel[1].addChapter(1,"Editorial", "Seite 1");

      alert (b.Kapitel[1].ueberschrift);
      alert (b.Kapitel[1].start_seite);

      würde allerdings trotzdem zu get-Methoden raten ;-)

      lg regenfeld

      1. ... wäre schöner, denn dann hast wirklich ein eigenes Objekt "Chapter", das dann auch mehr als nur eine Variable haben kann. Somit wird auch der "Aufruf" so wie du ihn anfangs haben wolltest: ...

        Wärend ich noch beim Tippen war (siehe voriges Posting), bist Du mir mit diesem Vorschlag zuvor gekommen!
        Thanx again!!!!

        Thomas

    2. äähhm, meintest du vielleicht so etwas in diese Richtung:

      ...

      Genau: So etwas in diese Richtung!

      Die Idee mit dem Pseudo-Konstruktor hat mir gefehlt.
      Vielen Dank für die rasche Antwort!!!!!

      Die Lösung mit der ich nun leben kann (sofern in diesem Thread nicht noch tollere Ideen kommen ;-) sieht in etwa so aus:

      // Konstruktor für das Buch
      function Book(Titel, Autor) {
        this.Titel = Titel;
        this.Autor = Autor;
        this.Kapitel = new Array();
        this.addChapter = addChapter;
      }

      function Chapter(Ueberschrift) {
       this.Ueberschrift = Ueberschrift;
      }

      function addChapter(Ueberschrift, Index) {
       if (Index==null) Index=this.Kapitel.length;
       this.Kapitel[Index] = new Chapter(Ueberschrift);
      }

      // Aufruf:
      b = new Book("SELFHTML","Stefan Münz");
      b.addChapter("Editorial",1);
      b.addChapter("Einführung");
      b.addChapter("HTML","drei");

      // Test:
      alert(b.Kapitel[1].Ueberschrift);
      alert(b.Kapitel[2].Ueberschrift);
      alert(b.Kapitel["drei"].Ueberschrift);

  2. gruss Thomas,

    die verwendung eines prototype-objektes, wie es
    regenfeld alias Bernhard Peissl aufzeigte, ist bei
    umfangreicher objektorientierter programmierung in
    JavaScript sehr elegent und (weil) speicherplatzschonend;

    ich zitiere aus meiner JavaScript-Bibel, da ich es selber
    nicht in anschaulichere worte zu fassen vermag -

    (ISBN 3-930673-56-8) /
      O'REILLY - JavaScript Das umfassende Referenzwerk -

    Seite 110 / Kapitel: 7.4 Objektprototypen:

    ab NNAV 3 und MSIE 3 "... gibt es noch einen anderen Weg, die
      Methoden, Konstanten und sonstigen Eigenschaften festzulegen,
      die von allen Objekten einer Klasse unterstuetzt werden sollen.
      Diese Technik besteht darin, die Methoden und sonstigen Eigen-
      schaften in einem PROTOTYPEOBJEKT fuer die Klasse zu definieren.
      Ein Prototypeobjekt ist ein besonderes Objekt, das mit der Konst-
      ruktorfunktion einer Klasse verbunden ist ..."

    bei regenfeld: "Book.prototype.addChapter"

    // Die Funktion, zu einer Instanzmethode machen
      Book.prototype.addChapter = addChapter;
      Book.prototype.getChapter = getChapter;

    // Konstruktor für das Buch
      function Book(Titel, Autor) {
        this.Titel = Titel;
        this.Autor = Autor;
        this.Kapitel = new Array();
      }
      // "Pseudo-Konstruktor" für die Kapitel
      function addChapter(Index, Ueberschrift) {
        this.Kapitel[Index] = Ueberschrift;
      }
      // Und auch ne Methode zum ausgeben:
      function getChapter (Index) {
        return this.Kapitel[Index];
      }
                                              "... und eine wichtige
      Besonderheit aufweist: Jede Eigenschaft, die fuer das Prototype-
      objekt einer Klasse definiert wird, erscheint wie eine Eigenschaft
      eines jeden Objekts dieser Klasse. Dies gilt fuer alle Eigen-
      schaften - unabhaengig davon, ob sie dem Prototyp vor oder nach
      der Definition der einzelnen Objekte hinzugefuegt werden. Die
      Eigenschaften des Prototypobjekts einer Klasse werden von allen
      Objekten dieser Klasse gemeinsam genutzt (d.h., die einzelnen
      Objekte erhalten keine eigene Kopie der Prototypeigenschaften,
      so dass der Speicherbedarf sehr gering bleibt)."

    noch wichtiger:

    "Die Eigenschaften des Prototypobjekts einer Klasse koennen ueber
      alle Objekte der Klasse ausgelesen werden. Auch wenn Sie Eigen-
      schaften dieser Objekte zu sein scheinen, sind sie dies nicht
      wirklich. Es gibt nur ein Exemplar jeder Prototypeigenschaft, und
      dieses Exemplar wird von allen Objekten einer Klasse geteilt. Wenn
      Sie eine dieser Eigenschaften eines Objekts 'lesend' ansprechen,
      lesen sie also tatsaechlich den gemeinsamen Wert aus dem Prototyp-
      objekt ..."  ACHTUNG  "... 'Setzen' Sie andererseits den Wert
      einer solchen Eigenschaft fuer ein bestimmtes einzelnes Objekt,
      erzeugen Sie tatsaechlich eine neue Eigenschaft dieses einen
      Objekts. Von nun an 'ueberlagert' oder versteckt die neu angelegte
      Eigenschaft die gemeinsame Eigenschaft des Prototypobjekts - aber
      nur in diesem einen Objekt."

    das Fazit der Autoren zu diesem Kapitel lautet:

    "Weil Prototypeigenschaften von allen Objekten einer Klasse gemein-
      sam benutzt werden, ist es im allgemeinen sinnvoll, sie zur Fest-
      legung von Eigenschaften zu benutzen, die fuer alle Objekte einer
      Klasse identisch sind. Deswegen kann man mit ihnen auf geradezu
      ideale Weise Methoden definieren.  ...  Wenn Ihre Klasse eine
      Eigenschaft mit einem sehr gebraeuchlichen Standardwert definiert,
      koennen Sie diese Eigenschaft samt ihrem Standardwert abenfalls
      im Prototypobjekt definieren. Dann koennen die wenigen Objekte,
      die von dem Standardwert abweichen sollen, ihr eigenes, nicht
      gemeinsames Exemplar der Eigenschaft erhalten. Diesem Exemplar
      kann dann jeweils ein vom Standard abweichender Wert zugewiesen
      werden."

    unter der folgenden URL findest Du einen Feature-Artikel, der
    eine eigene JavaScript-klasse zum einfacheren dynamischen
    erzeugen von HTML-tabellen-code beschreibt - aus heutiger
    sicht ist der code nicht allzu elegant, aber sehr interessant
    ist die tatsache, dass im klassen-konstruktor zur laufzeit
    nur die eigenschaften einer tabelle initialisiert werden, die
    auch tatsaechlich benutzt werden sollen;

    http://aktuell.de.selfhtml.org/artikel/javascript/table-obj/index.htm

    by(t)e by(t)e - peterS. - pseliger@gmx.net

    1. die verwendung eines prototype-objektes, wie es
      regenfeld alias Bernhard Peissl aufzeigte, ist bei
      umfangreicher objektorientierter programmierung in
      JavaScript sehr elegent und (weil) speicherplatzschonend;

      Vielen Dank für die wirklich ausführliche Erklärung!!!

      Wenn ich noch mal zusammenfassen darf:
      Methoden besser mit *prototype* definieren und nicht direkt im Konstruktor.

      Über die Begründung werde ich bei Gelegenheit nochmals meditieren ;-)

      Thanx!

      1. Grüssi,

        Wenn ich noch mal zusammenfassen darf:
        Methoden besser mit *prototype* definieren und nicht direkt im Konstruktor.

        Nunja, nochmal andersrum: Deine Klasse Book erbt quasi per default properties bzw. funktionen von seinem prototype-Objekt. Alles was im prototype "drin steht" steht allen Book-Objekten (gleichermassen) zur Verfügung. Wenn du nur VB kannst wird dir das nicht viel sagen, aber in Java würd sich das ca so lesen:

        class Book extends prototype {
           ...
        }

        oder so ähnlich, nur dass nicht explizit vererbt werden muss!

        Beispiel:

        function Circle {x,y,r} {
           this.x=x; this.y=y; this.r=r;
        }

        Circle.prototype.pi = 3.1415;

        Somit teilen Sich alle Circle Objekte diese Variable 'pi'. Wenn nun eine Klasse keine pi-Variable definiert, schaut der Javascript-Interpreter ob im prototype eine pi-Variable ist. Allerdings kann man diese pi auch in der Klasse überschreiben.

        Kurz gesagt *g* Das prototype-Objekt ist sowas wie ein Defaultkonstruktor, in dem Variablen/Funktionen vorgegeben werden können.

        Es können übrigens auch andere Objekte prototypes haben. Du kannst beispielsweise eine Spezial-methode für alle String-Objekte einführen:

        function myEndsWith ( c ) {
           return ( c == this.charAt( this.length - 1 ) )
        }

        String.prototype.endsWith = myEndsWith;

        var message = "huhu"
        message.endsWith(h);  // gibt false zurück
        message.endsWith(u);  // gibt true  zurück

        Über die Begründung werde ich bei Gelegenheit nochmals meditieren ;-)

        besser: iterieren ;-)

        lg regenfeld

        1. Ach ich Schussel, hab ich doch erst im lesen des eigenen Beitrags gesehen was ich da übersehen habe ;-)

          Hier sieht man schön den Unterschied:

          function Circle {x,y,r} {
             this.x=x;
             this.y=y;
             this.r=r;
          }

          Circle.prototype.pi = 3.1415;

          und vergleiche jetzt:

          function Circle {x,y,r} {
             this.x=x;
             this.y=y;
             this.r=r;
             this.pi=3.1415;
          }

          Das wär intuitiv sicher der erste Lösungsansatz gewesen, oder? 'pi' einfach als Instanzvariable zu definieren. Aber was passiert nun bei letzterer Variante? Bei *jedem* neuen Objekt (c = new Circle()) wird zusätzlich zu x , y , und r - die bei jedem Objekt sicherlich andere Werte haben - auch noch ein pi mit festem Wert initialisiert, völlig unnütz, da es sowieso für alle Objekte gleich ist !!

          Stell dir vor du willst die einen Bildschirmschoner basteln und ein paar Kreise ausgeben, die sich über den Bildschirm räkeln, sagen wir ca. 100.000.000  ;-)

          Hoffe die Sache ist nun etwas klarer ;-)

          lg regenfeld

        2. Nunja, nochmal andersrum: Deine Klasse Book erbt quasi per default properties bzw. funktionen von seinem prototype-Objekt. Alles was im prototype "drin steht" steht allen Book-Objekten (gleichermassen) zur Verfügung. Wenn du nur VB kannst wird dir das nicht viel sagen, aber in Java würd sich das ca so lesen:

          *nur* VB?!?! ;-)
          Die Sprache ist besser als ihr Ruf! Auch wenn in der Version 6 noch einige Sachen fehlen.

          Die Erklärung mit Java hilft mir nicht ganz weiter, aber auf C# umgelegt, handelt es sich dann offensichtlich um ein statisches Mitglied der Klasse, richtig?

          Ich war mir über die Bedeutung von prototype bis jetzt nicht ganz im Klaren; dachte, es dient nur dazu, um vorhandene Objekte um eigene Member zu erweitern. (vgl. 7.8, "JavaScript" von Stefan Koch, dpunkt.verlag)

          Danke, Jungs für Eure Erklärungen, die über meine eigentliche Fragestellung weit hinaus gegangen sind!! Eigentlich sollte dieser Thread für die Nachwelt im Archiv erhalten bleiben, oder?

          Über die Begründung werde ich bei Gelegenheit nochmals meditieren ;-)

          besser: iterieren ;-)

          Wenn ich nicht ganz am Holzweg bin, dann hab ich das doch recht schnell gecheckt, oder?!

          1. Hallo Thomas

            *nur* VB?!?! ;-)

            ich hab gewusst dass das kommt *fg* aber es war kein wertendes "und" nur die Feststellung, dass es dir nix sagen wird, wenn du ausser VB keine andere Programmiersprache kennst ;-)

            Die Erklärung mit Java hilft mir nicht ganz weiter, aber auf C# umgelegt, handelt es sich dann offensichtlich um ein statisches Mitglied der Klasse, richtig?

            Nun, es drückt eine Vererbungsbeziehung aus. Es erbt Methoden und Variablen von der Mutterklasse. jawa.awt.TextArea erbt beispielweise implizit (d.h. ohne dass du die Vererbung im Code niederschreiben musst) von java.awt.Component. Diese Component Klasse stellt dann Methoden zur Verfügung, um einen Objekt im Raum anzuordnen ( move() zum Bleistift ). Diese Move-Methode kann dann jedes Objekt verwenden, ohne sie explizit innerhalb seiner Klassendefinition zu spezifizieren. Somit ist eine Methode für das Herumschieben von Textfeldern, Buttons, Checkboxes, was auch immer verantwortlich. Ansonsten müsstest du in jeder klasse eine move()-Methode definieren ;-)

            Ich war mir über die Bedeutung von prototype bis jetzt nicht ganz im Klaren; dachte, es dient nur dazu, um vorhandene Objekte um eigene Member zu erweitern. (vgl. 7.8, "JavaScript" von Stefan Koch, dpunkt.verlag)

            Genau das tuts ja im Grunde, ich hab mich nur ein bissl kompizierter ausgedrückt ;-)

            Eigentlich sollte dieser Thread für die Nachwelt im Archiv erhalten bleiben, oder?

            Wird er ja auch, das Voting-System wurde ja praktisch erfolgreich geputscht ;-) Bisher wurde alles archiviert, und wenn sich da nix dran geändert hat, kommt auch dieser Thread ins Archiv!

            Über die Begründung werde ich bei Gelegenheit nochmals meditieren ;-)

            besser: iterieren ;-)

            Wenn ich nicht ganz am Holzweg bin, dann hab ich das doch recht schnell gecheckt, oder?!

            jop. :o)

            lg regenfeld

        3. Hallo,

          anstatt solche Weisheiten für Dich zu behalten solltest Du Dich gleich ans Werk machen und einen ausgedehnten Featuere-Artikel schreiben, damit die anderen auch was davon haben ;). Das ist kein Joke, soetwas fehlt hier im Selfraum wirklich (finde ich).

          Mein Beitrag zur prototypischen Diskussion :
          In JavaScript kann eine klassenbasierende Vererbung nicht umgesetzt werden, da es keine Klassen gibt, denn es fehlt an ausreichender Typisierung. Zudem können Objekte zur Laufzeit um Methoden und Eigenschaften beliebig erweitert werden. Dies sind zwei wesentliche Aspekte, warum JS kein Klassenkonzept kennt. Dennoch läßt sich über die prototypische Vererbung (von der Stanford Univerity für die Programmiersprache self entwickelt) eine Generalisierung und Spezialisierung umsetzen. Prototypische Objekte werden durch die Konstruktor-Methode new erzeugt. Dabei enthält das neu erzeugte Objekt lediglich einen Verweis auf den Prototypen. Prototypische Vererbung wird realisiert, indem man den prototypischen Verweis des Prototypen auf ein prototypisches Objekt zeigen läßt. Da es jeweils nur einen prototypischen Verweis gibt, sind Mehrfachvererbungen ausgeschlossen.

          function Tier() //- Prototyp Tier
          {
            this.belle = belle; //- Methode
          }

          Mensch.prototype = new Tier();
          function Mensch() //- Prototyp Mensch
          {
          }

          var ich = new Mensch(); //- Objekt ich besitzt nur einen prototypischen Verweis (auf Mensch);

          ich.belle();

          Erklärung :
          belle ist keine Methode des Objektes ich. Das Objekt ich besitzt lediglich einen Verweis auf den Prototypen Mensch (und der wiederum auf Tier). Der Interpreter sucht im Protypen Mensch nach der Methode belle. Wird er nicht fündig, so zieht er den prototypischen Verweis von Mensch, der auf den Prototypen Tier zeigt, für die Anforderungsuntersuchung heran.

          Im IE kann über den operator instanceof ermittelt werden, ob ein Objekt von einem bestimmten Prototypen erbt.

          if (ich instanceof Tier)
            alert("ich stamme vom Tier ab");

          Beim NS steht dieser Operator nicht zur Verfügung. Hier muß eine Funktion geschrieben werden, welche sämtliche prototypische Verweise auf Vererbungsabhängigkeiten hin untersucht. Dazu kann direkt auf den prototypischen Verweis __proto__ zugegriffen werden.

          function instanceof(obj, proto)
          {
            while (obj != null)
            {
              if (obj == proto.prototype)
                return true;
              else
                obj = obj.__proto__;
            }
          }

          if (instanceof(ich, Mensch))
            alert("Laut NS stamme ich vom Tier ab");

          Siehe auch vergleichende Lektüre : Internet World 08/2001 S. 86 die Macht der Objekte von Cai Ziegler.

          gruß
          tim