Dr.House: Objekt Instanzen speichern

Hallo!

viele Widgets werden wie folgt instanziiert:

var widget = new MeinWidget( dom_elem, {  
  // options...  
});

Wenn ich jetzt an anderer Stelle auf diese "widget" Objekt-Instanz zugreifen möchte und nur das DOM-Element als Suchschlüssel habe, wie kann ich diese zuvor sichern, wenn es mehr als eine gibt und das DOM-Element keine eindeutige ID besitzt.

Ich kann schließlich nicht einfach das DOM-Element selbst als Schlüssel verwenden, weil dieses sich jederzeit verändern kann:

  
var widgets = new Object();  
widgets[dom_elem] = new MeinWidget(dom_elem, ... );  

Wie geht das richtig, ohne jetzt jedem Element eine ID zuweisen zu müssen?

Danke!

  1. Hallo,

    var widgets = new Object();
    widgets[dom_elem] = new MeinWidget(dom_elem, ... );

      
    Echte Hashes in JavaScript (ECMAScript 5.1) gibt es nicht. Objekte können als Hashes verwendet werden, haben aber immer String-Schlüssel. Erst in kommenden ECMAScript-Versionen gibt es Hashes mit Objekten als Schlüssel geben. (Die können sich übrigens ruhig ändern – die Identität des Objektes bleibt ja dieselbe.)  
      
    Derzeit kann man einen Array mit den Instanzen pflegen. Anstatt direkt den Konstruktor zu verwenden, würde ich eine create-Funktion schreiben, die entweder die existierende Instanz zurückgibt oder eine neue erzeugt:  
      
    ~~~javascript
    MeinWidget.instances = [];  
    MeinWidget.create = function (element) {  
      var instances = MeinWidget.instances;  
      // Suche nach bestehender Instanz  
      for (var i = 0, l = instances.length; i < l; i++) {  
        var instance = instances[i];  
        // Angenommen, das Element wird in der Eigenschaft »element« gespeichert  
        if (instance.element == element) {  
          // Gebe bestehende Instanz zurück  
          return instance;  
        }  
      }  
      // Erzeuge neue Instanz  
      var widget = new MeinWidget(element);  
      MeinWidget.instances.push(element);  
      return widget;  
    };  
      
    var widget = createMeinWidget(document.getElementById('foo'));
    

    Das Konzept nennt sich Identity Map. Genau genommen könnte man obigen Code auch im Konstruktor notieren, weil dieser ein beliebiges Objekt zurückgeben kann, nicht notwendig die jüngst erzeugte Instanz. Gemäß dem »Prinzip der geringsten Überraschung« würde ich das aber wie oben in einer gesonderten Methode umsetzen. Diese kann man wie oben an der Konstruktorfunktion speichern – in anderen Programmiersprachen »statische Methode« oder »Klassenmethode« genannt – oder in einer davon unabhängigen Methode.

    Grüße,
    Mathias

    1. Vielen Dank schonmal für die Mühe!

      MeinWidget.instances = [];

      MeinWidget.create = function (element) {
        var instances = MeinWidget.instances;
        // Suche nach bestehender Instanz
        for (var i = 0, l = instances.length; i < l; i++) {
          var instance = instances[i];
          // Angenommen, das Element wird in der Eigenschaft »element« gespeichert
          if (instance.element == element) {

        
      Das 'element' befindet sich nur in 'instance' (siehe push), nicht in instance.element.  
      ~~~javascript
        
      
      >       // Gebe bestehende Instanz zurück  
      >       return instance;
      
      

      'instance' enthält das DOM-Element, nicht aber die dazugehörige Instanz.

        
      
      >     }  
      >   }  
      >   // Erzeuge neue Instanz  
      >   var widget = new MeinWidget(element);  
      >   MeinWidget.instances.push(element);  
      >   return widget;  
      > };  
      >   
      > var widget = createMeinWidget(document.getElementById('foo'));
      
      

      Der Chrome meckert bei "createMeinWidget", müsste es nicht "MeinWidget.create" heissen?

      1. Das 'element' befindet sich nur in 'instance' (siehe push), nicht in instance.element.
        'instance' enthält das DOM-Element, nicht aber die dazugehörige Instanz.
        Der Chrome meckert bei "createMeinWidget", müsste es nicht "MeinWidget.create" heissen?

        Nachdem du das alles rausgefunden hast, sollte es doch leicht sein das zum laufen zu bringen!

        Allerdings hast du hier bei vielen Elementen bei jedem Zugriff auf dein widget viel zu suchen.
        Wenn dein Zugriff immer über das Element erfolgt, kannst du es auch gleich dort speichern.

        1. Allerdings hast du hier bei vielen Elementen bei jedem Zugriff auf dein widget viel zu suchen.
          Wenn dein Zugriff immer über das Element erfolgt, kannst du es auch gleich dort speichern.

          Kann man Javascript-Objekte in einem DOM-Element ablegen?

          1. Hallo,

            Kann man Javascript-Objekte in einem DOM-Element ablegen?

            Ja, man kann einfach neue Eigenschaften erzeugen. Das ist eine Grundfunktionalität von JavaScript-Objekten, und bei DOM-Knoten ist das nicht abgeschaltet (d.h. sie sind »extensible«).

            element.foo = {};

            Allerdings bringt es seine eigenen Fallstricke mit, DOM-Knoten mit eigenen JavaScript-Objekten zu verknüpfen und Arbeitsdaten an DOM-Knoten zu speichern. DOM-Land und JavaScript-Land sind hinsichtlich des Speichers getrennt und haben eigene Methoden der Speicherbereinigung (Garbage Collection). Wenn man im DOM Referenzen auf JavaScript-Objekte erzeugt, so verhindert man, dass die Objekte abgebaut werden, solange das Element existiert.

            Das ist kein Drama, man muss nur daran denken, diese Referenzen beim Abbauen der Objekte zu entfernen. In älteren Browsern war das noch ein absolute No-Go, weil es Speicherlecks erzeugt hat, heute dürfte die Situation entspannter sein. Trotzdem würde ich dazu raten, die Zuordnung im JavaScript-Land zu speichern.

            Grüße,
            Mathias

            1. Wenn man im DOM Referenzen auf JavaScript-Objekte erzeugt, so verhindert man, dass die Objekte abgebaut werden, solange das Element existiert.

              Andersherum aber doch auch, wenn man DOM Referenzen in JavaScript-Objekten speichert, verhindert man, daß die Elemente abgebaut werden, solange das Objekt existiert, auch wenn sie aus dem DOM entfernt wurden.

              Das ist kein Drama, man muss nur daran denken, diese Referenzen beim Abbauen der Objekte zu entfernen.

              Das muss man aber auch mit dem MeinWidget.instances-Array machen.

              Also alles halb so wild.

              1. Andersherum aber doch auch, wenn man DOM Referenzen in JavaScript-Objekten speichert, verhindert man, daß die Elemente abgebaut werden

                Klar, das stimmt. Aber man ist es eher gewöhnt, im JavaScript-Land aufzuräumen. Das JavaScript-Land ist übersichtlicher und einfacher zu verwalten – ein Objekt steckt im dafür vorgesehenen globalen Speicher window.MeinWidget.instances oder nicht. Es gibt natürlich fiese Fallstricke wie Closures, die Objekte einschließen, die sonst nicht ausgehend vom globalen Objekt erreichbar sind.

                Dass man die Referenzen im DOM-Land genauso aufräumen muss, ist noch nicht so breit bekannt. Das DOM-Land ist groß und unübersichtlich, man hat gerne tausende tief verschachtelte Elemente. Es gibt viele Möglichkeiten, versteckt auf Objekte zu verweisen. Event-Handler z.B. sind fies, sie verknüpfen DOM-Knoten mit Funktionen, die häufig größere Objekte einschließen. Nicht ohne Grund haben solche Closures in älteren Browsern Memory-Leaks ausgelöst.

                Ganz ohne beidseitige Referenzen zwischen JavaScript und DOM kommt man nicht aus, aber ich würde versuchen sie zu minimieren. jQuery und andere Bibliotheken bieten mit .data() die Möglichkeit, Objekte/Werte mit DOM-Elementen zu verknüpfen. Dabei wird aus guten Gründen keine direkte Objektreferenz erzeugt, sondern nur ein GUID am Element gespeichert (ein winziger String). Die Nutzdaten selbst werden in einem JavaScript-Hash gespeichert.

                Mathias

                1. Klar, das stimmt. Aber man ist es eher gewöhnt, im JavaScript-Land aufzuräumen. Das JavaScript-Land ist übersichtlicher und einfacher zu verwalten – ein Objekt steckt im dafür vorgesehenen globalen Speicher window.MeinWidget.instances oder nicht.

                  Hmm, also das window-Objekt ist ja genauso wie ein HTML-Element-Objekt kein natives JS-Objekt.
                  Schon klar, du meinst mehr das window-Objekt als globales Objekt, aber ich würde da jetzt keine Grenze ziehen zw. reinen JS-Objekten und (Browser-)API-Objekten.

                  Dass man die Referenzen im DOM-Land genauso aufräumen muss, ist noch nicht so breit bekannt.

                  Das würde ich bezweifeln, wenn jemand überhaupt Referenzen auflöst, warum sollte er die am Element-Objekt nicht beachten?

                  Das DOM-Land ist groß und unübersichtlich, man hat gerne tausende tief verschachtelte Elemente. Es gibt viele Möglichkeiten, versteckt auf Objekte zu verweisen. Event-Handler z.B. sind fies, sie verknüpfen DOM-Knoten mit Funktionen, die häufig größere Objekte einschließen.

                  Das ist dann vermutlich der Punkt, wo man Speicherprobleme bekommt und überhaupt erst daran denkt die Referenzen aufzulösen.

                  Nicht ohne Grund haben solche Closures in älteren Browsern Memory-Leaks ausgelöst.

                  Was heißt nicht ohne Grund? Entweder sind es echte Zirkelreferenzen, die kann der Browser nicht auflösen, oder Bugs, z.B. dass beim entfernen des Elementes der Eventhandler nicht dereferenziert werden. Da gab (vor allem beim IE) und gibt es sicher welche.

                  1. Hallo!

                    Nicht ohne Grund haben solche Closures in älteren Browsern Memory-Leaks ausgelöst.

                    Was heißt nicht ohne Grund? Entweder sind es echte Zirkelreferenzen, die kann der Browser nicht auflösen, oder Bugs, z.B. dass beim entfernen des Elementes der Eventhandler nicht dereferenziert werden. Da gab (vor allem beim IE) und gibt es sicher welche.

                    Das Problem ist m.W., dass die tatsächlichen DOM-Objekte der in C++ implementierten Speicherverwaltung unterliegen, während JavaScript-Objekte der Garbage Collection der JavaScript-Engine unterliegen. Das sind ursprünglich zwei Systeme, die durch das DOM verbunden sind. Umso mehr, wenn Eigenschaften oder Variablen auf DOM-Objekte verweisen oder JavaScript-Objekte an DOM-Objekte gehängt werden.

                    Diese Verwirrung aufzulösen scheint immer noch ein Problem zu sein. Deshalb ist eine Architektur-Idee von Blink: Experiment with moving the DOM into the JS heap. Damit gäbe es keine Verdoppelung der DOM-Objekte und die GC der JS-Engine könnte eigenmächtig DOM-Objekte aus dem Speicher räumen.

                    Grüße,
                    Mathias

                    1. Damit gäbe es keine Verdoppelung der DOM-Objekte und die GC der JS-Engine könnte eigenmächtig DOM-Objekte aus dem Speicher räumen.

                      Die Verdoppelung an sich ist ja an und für sich nicht so schlimm. Wenn alle referenzierten DOM-Objekte auch wieder dereferenziert werden ist alles in Butter. Man darf nur keine Stelle vergessen, auch auf C++ Seite.
                      Bei IE (bis 6, dann kahm C#, dort weiss ich es nicht mehr) war das DOM als COM realisiert. Jeder Objekt hat dort einen Referenzcounter, der geht beim Anlegen auf 1, bei jeder weiteren Stelle, wo dieses Objekt gespeichert ist muss AddRef gerufen werden, das inkrementiert den Counter. Wird das Objekt an einer Stelle nicht mehr benötigt, wird Release aufgerufen, und damit der Counter wieder runtergezählt. Ist dieser 0, löscht sich das Objekt selbst aus dem Speicher. Wird das Objekt nach JS weitergereicht, wird in dessem Speicher(COM Apartment) ein Proxy-Objekt angelegt. Diese leitet alle Methodenaufrufe an das eigentliche Objekt weiter, auch AddRef und Release, nur das der Proxy einen eigenen Counter verwaltet und den am eigentlichen Objekt nur einmal erhöht bzw. runterzählt, beim Anlegen bzw. wenn der Proxycounter auf 0 geht.
                      Lange Rede kurzer Sinn, das ist nicht so der Unterschied zu einer Garbage Collection, nur dass der Speicher gleich freigegeben wird wenn der Counter auf 0 geht. Aber die Referenzen müssen auch wieder aufgelöst werden, in beiden Fällen, sonst hat man ein Zombieobjekt.

                      1. Moin,

                        sofern ich mich recht erinnere, hat man bei C# auf Referenzzähler aus Performance-Gründen verzichtet. Der Compiler kann starke und schwache Verweise unterscheiden und irgendwie macht er daran die ganzen Aufräumarbeiten fest (Dispose, Finalize). Bei Windows-RT beruht dann wieder C# teileweise auf COM-Wrapper. Sorry, hat jetzt nichts mit dem Ursprungsbeitrag zu tun, wollte das nur mal erwähnen.