Der Daniel: Was zum Geier hab ich da aufgeführt? Javascript Objekte und andere Irrsinnigkeiten...

Hallo,

Ok... eigentlich suchte ich eine Shorthand Methode um ein Objekt in einem Objekt zu realisieren, nach dem Muster

// AUSGABE 1
/*
GLOBAL_VARS = {
	einObj: {i: 0}
}
*/

Wollte folgenden Code

// BSP 1
const GLOBAL_VARS = {};
if (!GLOBAL_VARS.einObj) {
	GLOBAL_VARS.einObj = {};
}
GLOBAL_VARS.einObj.i = 0;

mit

// BSP 2
const GLOBAL_VARS = {};
GLOBAL_VARS.einObj.i = 0;

abkürzen, was natürlich nicht funktionierte, da in BSP 2 einObj ja noch nicht definiert wurde und daher einen Fehler auswirft.

Wirklich spannend wurde es, als ich folgende Methode probierte:

// BSP 3
GLOBAL_VARS.einObj = {}.i = 0;

...was mit GLOBAL_VARS = {einObj: 0} quittiert wurde - was zum Geier geht da vor?

Und gibt's da eine Methode um das etwas klobige if-Statement in BSP 1 eleganter zu umschiffen?

LG Daniel

  1. Hallo Daniel,

    wenn es denn unbedingt ein GLOBAL_VARS Objekt sein muss... es gibt andere Methoden (Module).

    Wie auch immer. Das Pattern ist eigentlich dieses:

    window.GLOBAL_VARS = window.GLOBAL_VARS || {};
    GLOBAL_VARS.einObj = GLOBAL_VARS.einObj || {};
    
    GLOBAL_VARS.einObj.i = 47;
    

    GLOBAL_VARS || {} - was heißt das? Schau ins Wiki unter "Was ist Wahrheit".

    Wenn GLOBAL_VARS noch undefiniert ist, findet sich darin der Wert undefined. Dieser Wert ist falsy, deswegen wertet der || Operator auch noch seine rechte Seite aus und liefert diesen Wert zurück. Mehr dazu im Wiki unter Wahrheitstabellen und Kurzschlussreaktionen.

    Warum window.GLOBAL_VARS? Weil Du sonst eine Fehlermeldung bekommen kannst, dass es diese Variable nicht gibt. Insbesondere im strict mode. Und var GLOBAL_VARS kannst Du ja nicht schreiben, wenn Du nicht weißt, ob die Variable schon da ist.

    Dieses Hantieren mit || ist eine Spezialität von JavaScript. Das Ergebnis von || und && ist nicht true oder false, sondern der Wert des linken oder rechten Operanden. Und das nutzt man im oben gezeigten Pattern aus.

    1. Ist GLOBAL_VARS schon da, bleibt es, wie es ist. Ist es noch nicht da, wird es mit einem leeren Objektliteral initialisiert.
    2. Ist GLOBAL_VARS.einObj schon da, blebt es wie es ist. Ist es noch nicht da, wird es mit einem leeren Objektliteral initialisiert.

    Und was ist mit deinem Schlussbeispiel?

    GLOBAL_VARS.einObj = {}.i = 0;

    Böse ist das. Böse und Gemein!

    {} ist ein Objektliteral und erzeugt ein leeres Objekt, mitten im großen Datenhaufen (a.k.a. Heap). Ein Verweis darauf wird ganz oben auf den Wertestapel gelegt. {}.i=0 erzeugt in diesem leeren Objekt ein Property i und speichert darin den Wert 0. Nun kommt das Erbe der 1960er und der Sprache C - der Operator = ist ein Operator wie alle anderen auch, relativ niedrig priorisiert und rechts-assoziativ. Rechts-assoziativ heißt, dass c = b = a wie c = (b = a) zu lesen ist, und nicht wie (c = b) = a (was in JavaScript eh Blödsinn wäre, aber in C++... oh je!). In deinem Fall wird also zuerst {}.i=0 ausgeführt. Zum Erbe gehört auch dies: Jeder Operator Hat Ein Ergebnis. Auch der Zuweisungsoperator. Der Wert von {}.i = 0 ist der zugewiesene Wert, also 0. Dieser Wert ersetzt den Verweis auf das vorhin angelegte Objekt, und wird dann an GLOBAL_VARS.einObj zugewiesen. Danach ist das Statement zu Ende. Dieses kleine Objekt, dem Du eine Eigenschaft i verpasst hast, hat nun keinen mehr, der sich für es interessiert. Und so schwimmt es einsam und traurig irgendwo im Heap herum, ohne eine Rettungsleine, bis der gierige Garbage Collector vorbeischwimmt und es verschlingt. Armes Objekt 😢.

    Anders gesagt:

    GLOBAL_VARS.einObj = 0;

    tut genau das gleiche, ohne ein Waisenobjekt zu hinterlassen.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. WOW - Danke für die Abhandlung

      ...ja ja, die liebe Not mit globalen Variablen...

      Problem ist eben, dass wenn mehrere Funktionen z.B. ein und denselben Index in- oder dekrementieren, dies ohne globaler Zuflucht schlicht nicht möglich ist. Diese globaler Index muss dann in der Funktion ärgerlicherweise sogar per hard code eingefügt werden und kann nicht als Parameter übergeben werden

      let index = 0;
      
      function incrementIndex(i) {
      	return i++;
      }
      incrementIndex(index); // globaler index immer noch 0 => ;( rührt index außerhalb von function scope nicht an
      
      function incrementIndexHardCoded() {
      	return index++;
      }
      incrementIndexHardCoded(); // globaler index nun 1 => :) bezieht sich auf index außerhalb von function scope 
      

      Damit diese nicht massenweise den Namespace zu verschmutzen, keimte in mir eben die Idee, alle globalen Variablen in EINEM globalen Objekt zusammenzufassen, eben GLOBAL_VARS.

      Bin für bessere Ideen aber jederzeit offen! 😀


      ...und was mit:

      GLOBAL_VARS.einObj = {}.i = 0;

      unter der Haube passiert, habe ich zugegeben nicht ganz verstanden. Rechts assoziativ wird diese Zauberformel umgewandelt zu

      GLOBAL_VARS.einObj = {i = 0};

      ? und wie ginge es dann weiter?

      1. Hallo,

        ...und was mit:

        GLOBAL_VARS.einObj = {}.i = 0;

        unter der Haube passiert, habe ich zugegeben nicht ganz verstanden. Rechts assoziativ wird diese Zauberformel umgewandelt zu

        GLOBAL_VARS.einObj = {i = 0};

        nein, die geschweiften Klammern wären so syntaktisch falsch. Man kann aber tatsächlich (runde!) Klammern setzen, um es etwas deutlicher zu machen:

        GLOBAL_VARS.einObj = ({}.i = 0);

        Zuerst wird der Ausdruck in der Klammer ausgewertet. Also ein leeres Objekt erzeugt, dem eine Eigenschaft i angehängt und dieser der Wert 0 zugewiesen. Der Zuweisungs-Operator hat aber auch einen Rückgabewert, und der ist relevant, wenn die Zuweisung innerhalb eines Ausdrucks vorkommt, wie hier. Der Rückgabewert des Zuweisungs-Operators ist der zugewiesene Wert, also 0. Dieser Wert 0 wird nun an GLOBAL_VARS.einObj zugewiesen, und das eben erzeugte Objekt fällt auf den Boden und zerschellt, weil es keinen Namen hat, mit dem man es ansprechen könnte.

        Live long and pros healthy,
         Martin

        --
        Klein φ macht auch Mist.
        1. Hallo Martin,

          fällt auf den Boden und zerschellt

          ts, meinst Du denn der Garbage Collector frißt was vom Boden? Tatsächlich bleibt das Objekt unverändert auf dem Heap vorhanden - es gibt aber keine Referenz mehr darauf. Und das stellt der GC fest, wenn er vorbeikommt, und frisst es auf.

          Wie er das feststellt, ist dagegen kaum erklärbar. Effiziente Garbage Collection ist arkane Kunst, und wird von den Runtimeherstellern eifersüchtig gehütet.

          Rolf

          --
          sumpsi - posui - obstruxi
      2. Hallo Daniel,

        Bin für bessere Ideen aber jederzeit offen!

        Für bessere Ideen müsste man mehr über dein Gesamtkunstwerk wissen.

        Wenn Du Eventhandler mit onclick oder onsonstwas definierst, wirst Du kaum eine Chance haben. Denn die brauchen globale Funktionen und die müssen dann globale Objekte verwenden.

        Aber wenn Du ordentlich mit unobtrusive Script arbeitest, d.h. deine Eventhandler mit addEventListener registrierst, kannst Du Modularisierung mit Hilfe einer IIFE betreiben. Oder Du stellst das Ganze in ein <script type="module">...</script> Element (ECMAScript-Modul, nicht im Internet Explorer verfügbar) und brauchst Dir keine Gedanken um globalen Müll zu machen, denn globale Variablen eines Moduls bleiben im Modul. Zu IIFEs haben wir Artikel im Wiki, zu ECMAScript-Modulen nicht. Dafür hatte ich bisher keine Zeit. Aber das Wilde Weite Web hat viele Infos dazu.

        Das Problem mit dem Hardcoding kannst Du nur über Objekte lösen, denn eine Übergabe per Referenz kennt JavaScript nicht. Aber ein Objekt hast Du ja nun.

        D.h. wenn Du GLOBAL_VAR.foo.bar.i inkrementieren willst, könntest Du eine Funktion "erhöhe" schreiben, die so aussieht:

        function verändere(obj, eigenschaft, wert) {
           obj[eigenschaft] = obj[eigenschaft] + wert;
        }
        
        // Inkrementieren von i mit
        verändere(GLOBAL_VAR.foo.bar, "i", +1);
        // Dekrementieren von i mit
        verändere(GLOBAL_VAR.foo.bar, "i", -1);
        

        Aber schick ist das nicht wirklich.

        Du könntest es auch so machen, dass Du ein Zählerobjekt mit Methoden erstellst. Das folgende ist "modernes" JavaScript; wenn Du auch den Internet Explorer unterstützen willst, musst Du auf die alte Syntax mit Konstruktorfunktion und Prototyp zurückfallen.

        // Einmalig an geeigneter Stelle
        class Counter {
           constructor(startwert) {
              this.value = startwert;
           }
           increment() { this.value++; }
           decrement() { this.value--; }
        }
        
        // Verwendung
        GLOBAL_VAR.foo.bar.zähler = new Counter(0);
        GLOBAL_VAR.foo.bar.zähler.increment();         // Direkt inkrementieren
        console.log(GLOBAL_VAR.foo.bar.zähler.value);  // 1
        erhöhe(GLOBAL_VAR.foo.bar.zähler);             // Aufgabe delegieren
        console.log(GLOBAL_VAR.foo.bar.zähler.value);  // 2
        
        function erhöhe(z) {
           z.increment();
        }
        

        Rolf

        --
        sumpsi - posui - obstruxi
      3. Moin,

        Problem ist eben, dass wenn mehrere Funktionen z.B. ein und denselben Index in- oder dekrementieren, dies ohne globaler Zuflucht schlicht nicht möglich ist.

        Globale Zuflucht? Was soll das denn sein?

        ...und was mit:

        GLOBAL_VARS.einObj = {}.i = 0;

        unter der Haube passiert, habe ich zugegeben nicht ganz verstanden. Rechts assoziativ wird diese Zauberformel umgewandelt zu

        GLOBAL_VARS.einObj = {i = 0};

        ? und wie ginge es dann weiter?

        GLOBAL_VARS.einObj = {}.i = 0;
        // führt zu:
        GLOBAL_VARS.einObj = 0;
        
        GLOBAL_VARS.einObj = {i = 0};
        // Syntax Error
        
        GLOBAL_VARS.einObj = {i: 0};
        // führt zu
        GLOBAL_VARS.einObj.i = 0;
        

        Viele Grüße
        Robert

    2. Hi Rolf

      Das Pattern ist eigentlich dieses:

      window.GLOBAL_VARS = window.GLOBAL_VARS || {};
      GLOBAL_VARS.einObj = GLOBAL_VARS.einObj || {};
      
      GLOBAL_VARS.einObj.i = 47;
      

      Wobei man das in modernen Browsern mit dem nullish coalescing operator noch abkürzen kann:

      (window.GLOBAL_VARS ??= {}).einObj ??= {}
      

      In A ?? B wird der Ausdruck B nur ausgewertet, wenn A null oder undefined ist, sonst wird der Wert von A zurückgegeben.

      Der Zuweisungsausdruck A ??= B entspricht A ?? (A = B).

      Viele Grüße,

      Matthias

      1. Hallo Ingrid,

        ich habe dem Wiki mal einen Artikel zum nullish coalescing Operator (??) hinzugefügt.

        Viele Grüße,

        Matthias

        1. Hallo Matthias,

          ich habe dem Wiki mal einen Artikel zum nullish coalescing Operator (??) hinzugefügt.

          Vielen Dank und herzliche Grüße

          Matthias Scharwies

          --
          PS: Meine Mutter fand diesen Vornamen so schön und (in Norddeutschland) so selten. Auf dem Spielplatz unserer Neubausiedlung waren wir vier, später in der 5. Klasse drei Matthiase! 😀
      2. Hallo Matthias,

        du hast schon recht, den kann man verwenden. Ich habe ihn hier aus zwei Gründen nicht erwähnt:

        • der TO war schon mit einfacheren Dingen ausgelastet
        • man muss sich gut überlegen, wie man bei Gebrauch solcher Dinge mit Browsern umgeht, die sie nicht kennen. Jedes moderne Feature, das ich verwende, muss ich in Altbrowsern polyfillen oder für Altbrowser den JS-Teil abschalten. Und ich muss für jedes JS Feature, dass ich nutze, genau wissen, ob die Altlasten des Internet darüber stolpern oder nicht. Deswegen programmiere ich eher konservativ, und spare mir den Einsatz von Babel, Typescript oder ähnlichem. Das kann man sicherlich auch anders sehen.

        Irgendwann wird sicherlich der Punkt kommen, an denen IE Benutzer nur noch eine Meldung der Art "This page is modern and works only with a modern browser" zu sehen bekommen...

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hi Rolf,

          das stimmt natürlich. Mein Beitrag war auch bloß als Ergänzung für Mitleser gedacht, die mit der Syntax eventuell noch nicht vertraut sind.

          Zu mehr reicht's bei mir im Moment eh nicht, da mein Schwerpunkt Computer Vision/Machine Learning doch eine ganz andere Baustelle ist. 😉 Ich verfolge aber natürlich trotzdem die Weiterentwicklung der Sprache und da bleibt sowas mal hängen. Inwieweit das auch praktisch einsetzbar ist, habe ich mal gekonnt ignoriert…

          Grundsätzlich empfinde ich den von dir vorgeschlagenen Weg der konservativen Programmierung allerdings als weitaus schwieriger und aufwändiger als die Alternative.

          Das mag natürlich mit meinem Mangel an praktischer Erfahrung zusammenhängen, aber ich fühle mich komplett damit überfordert, einschätzen zu können, welche Features in welchen Browsern funktionieren und welche Browser für welche Zielgruppen überhaupt noch relevant sind.

          Es erscheint mir viel einfacher, die kompatible Untermenge der Sprache von Leuten definieren zu lassen, die mehr Ahnung davon haben als ich, und dann meinen Code in die entsprechende Zielsprache übersetzen zu lassen.

          Sofern man mehr als ein paar hundert Zeilen Code hat, will man den ja in der Regel ohnehin in mehrere Module zerlegen, die dann von einem Bundler für die Auslieferung an den Client wieder zusammengefügt werden. Wenn ich aber bereits so einen Buildschritt habe, ist es kaum Mehraufwand, noch einen Transpiler drüberlaufen zu lassen.

          Der Aufwand, ein entsprechendes System aufzusetzen ist natürlich nicht Null, aber zumindest Webpack mit Babel lässt sich mittlerweile relativ leicht einrichten. Für die meisten Projekte dürfte die Standardkonfiguration ausreichend sein, so dass man nicht übermäßig viel Zeit in das Erlernen dieser Tools investieren muss.

          Den Code zu übersetzen entlässt mich als Entwickler nicht komplett aus der Verantwortung und es bringt auch immer einen gewissen Overhead mit sich, aber zumindest kann ich für die Entwicklung meiner Programme die am besten geeigneten Sprachmittel verwenden, ohne mich dabei danach richten zu müssen, ob irgendein Feature zum aktuellen Zeitpunkt produktiv verwendet werden kann oder nicht – zumal sich das im Zweifel auch schneller ändert, als ich meinen Code anpassen kann…

          Wenn man sich mit den Fähigkeiten der Browser so gut auskennt wie du, verschiebt sich die Schwelle, ab der der Einsatz solcher Tools als lohnenswert erscheint, wahrscheinlich ein ganzes Stück, aber ich stelle mir vor, dass so ein Übersetzungsschritt gerade für Leute mit weniger Erfahrung die Komplexität der Aufgabe insgesamt reduziert.

          Viele Grüße,

          Matthias