Call the Doctor!: Frage zum Wiki-Artikel „remove“

problematische Seite

🙋🏻 Ich hab’ da ein Problem. Mit Element.remove(). Das „will nicht“, nicht jedes Mal. Und ich suche jetzt mögliche Gründe dafür.

Ein brauchbares Beispiel kann ich nicht liefern: damit an der Hand wäre die Frage wahrscheinlich ja schon erledigt. Also eine ungefähre Beschreibung.

HTML, auf das IMO Wesentliche reduziert, ist nach folgendem Schema aufgebaut:

…
<div id=…a>
<element> … </element>
<div id=…b>
</div>
</div>

Das Skript, das da spielt, besteht (sozusagen aus Gründen der Modularisierung) aus einer Folge von IIFEs, die wiederum einander ähnlich sind:

…
	const aside = (() => {
		const aside = document.createElement('aside');
		const theAside = source.getElementById('…');
		…
		theAside.remove();
		return aside;
	})();
…

Der Rückgabewert bzw. der Name des const entspricht „aus Gründen der Bequemlichkeit“ dem Element, in dem die extrahierten Elemente abgelegt und später dargestellt werden (sollen).

Das .remove() ist eigentlich nur deshalb drin, weil ich mich, Schritt für Schritt, voran bewege und so sehen kann, „wo noch Baustelle ist“. — Dachte ich.

Aber plötzlich, nach einem halben Dutzend solcher IFFEs, die tun, was sie tun sollen, widersetzt sich eines dieser „Id-DIV“ der Entfernung. Im Browser sehe ich die darin befindlichen Daten doppelt: da, wo sie schon waren und dannn nochmal in dem Element, das ich dafür vorgesehen habe.

Natürlich findet sich das auch im Inspektor des Browsers. Und die „Vorlage“ hat immer noch ihe id.

Versuchsweise habe ich zum Ende des IIFE dann noch so etwas eingehängt:

console.info(theAside, source.getElementById(…));
source.getElementById(…).remove();
console.info(source.getElementById(…));

Das Ergebnis: das erste Info zeigt zweimal das Gleiche an, das zweite Info liefert undefined. Dumm nur, daß document.getElementById(…) das anders sieht. Und daß man dort mit document.getElementById(…).remove() den Quälgeist dann doch los wird.

Komisch kommt mir auch vor, daß die Doubletten ja nicht (extra) kopiert werden. Die sollten mittels .append() doch eigentlich „wandern“?

Irgendwie, scheint mir, hängt es an der Skript-Ausführung. Aber wenn, wieso dann bei einem von mehreren ähnlich aufgebauten und ähnlich behandelten Bereiche? — Kommt da jetzt die Feuerwehr? Weil 🤯?

Wie auch immer: habt Ihr eine Idee, woran .remove() scheitern kann? Außer dem, was ich hier skizziert habe, passiert in meinem Skript nämlich nichts „mit den Id“ und ihren Elementen.


BTW: mit IIFEs kann man u.a. auch etwas wie einen Debug-Modus bewerkstelligen, wie man ihn von Programmiersprachen wie z.B. die diversen C-Dialekten kennt: #if debug … o. ä.:

if (__debug__) (() => { … })();

„mit ohne“ Rückgabewert, sonst

if (__debug__) {
(() => { … })();
}

Und wenn man __debug__ im Quelltext sucht, ist es auch schnell wieder (komplett) ausgebaut.

  1. problematische Seite

    “Call the Doctor, I need h(er|im)!”

    Da hab’ ich mir doch ganz „elegant“ selbst ins Bein geschossen!

    Der Grund war, daß ich

    1. meinen Kram „passend“ benannt habe: const article soll <article> beherbergen. Usw. Auch innerhalb der IIFE behielt ich das bei, denn das Ziel, den “globalen Namensraum“ hübsch sauber und übersichtlich zu halten, war ja erreicht.

    2. aber dann kam mir dabei die „zehn Finger Automatik“ in die Quere. Wer gelernt und verinnerlicht hat, daß man mit allen Fingern die Tasten betätigt, wird es kennen: am besten funktioniert das, wenn man in so einen “flow” kommt, wenn die Gedanken nur oberflächlich beim Tippen sind und sonst irgendwo drum herum kreisen. Das Blöde daran: man ist es so gewöhnt, (fast) überall ein document. vor so viele Dinge zu tippen, daß das ganz schnell ganz automatisch passieren kann …

    Fazit: beim „nächsten Mal“ werde ich ein, zwei kleine Änderungen vornehmen. Beispielsweise wird <main> in einem Objekt abgelegt, das nur ähnlich wie aber keinesfalls main benannt wird. Dann setzt es eher (und hierbei hilfreiche) Einträge im Error-Log.

    1. problematische Seite

      Hallo,

      man ist natürlich immer versucht, den globalen Namensraum sauber zu halten. Ob man dafür aber seinen Code in tausende kleiner IIFEs fragmentieren muss?

      Zum einen bietet es sich zum Sauberhalten des Namensraums an, seine Scripte mit type="module" als ES6-Module laufen zu lassen. Damit ist der Weg zum globalen Namensraum so gut wie versperrt – es sei denn, man besteht mittels window.foo = "bar"; darauf, sich dort breitzumachen.

      ES6-Module haben auch den Vorteil, im defer-Modus geladen zu werden. Heißt: egal wo sie stehen, sie werden erst ausgeführt, wenn das DOMContentLoaded-Event fällig ist (aber vor diesem Event).

      Innerhalb eines ES6-Moduls kann man für weitere Reinlichkeit Klassen definieren und Daten darin als Eigenschaften halten.

      Und wenn man ohnehin auf var verzichtet, reicht es zum Sauberhalten des Modul-Namensraums auch, den Code in geschweifte Klammern zu setzen.

      {
         const x = 3;
         console.log(x);
      }
      console.log(x);   // Error, sofern x nicht vorher nochmal definiert wurde
      

      Was deiner aside-IIFE natürlich nichts nützt, du möchtest das aside-Element ja nachher im äußeren aside haben. Für deinen debug Modus hingegen schon.

         if (__debug__) {
            // bla bla
         }
      

      Der bla bla Code muss nicht in eine IIFE.

      Was hier übrigens ganz praktisch ist, ist ein instrumentierbarer Debug.

      const __debug__ = {
         foo: localStorage.getItem("modul3.thema4.debug") == true;
         bar: localStorage.getItem("modul3.thema7.debug") == true;
      }
      ...
      
      if (__debug__.foo) {
         // Debughilfen für modul3, thema4
      }
      

      Solche Dinge kann man während der ersten Entwicklungsrunden im Code drinbehalten und dann auch im Produktionssystem einzelne Bereiche mit console.log fluten. Oder auch anders loggen – gerade bei mobilen Anwendungen ist keine Konsole da, man kann auch Logsätze mit fetch an den Server POSTen. Ist natürlich nicht schnell, aber darum geht's beim Debuggen nicht.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. problematische Seite

        Ich danke für die Antwort!

        Dazu noch ein „Re“ – sozusagen wegen dieses Status Quo. (Und weil Groucho mal wieder einen Auftritt haben wollte.)

        Hmmm… Module. Ein Weiteres „JS-Ding“, das ich nicht (noch nicht?) angesehen habe. Der Bedarf ist (vorerst?) nicht groß genug, ich stecke die da nötige Energie lieber erst mal in andere JS-Baustellen. Aber merken werde ich die mir.

        Wegen des Aufwands: so arg viele IIFEs sind es ja nicht. Es werden vielleicht ein Dutzend oder zwei. Und: auf die bin ich gekommen, weil mir das mit dem Klammern ({ und }) nicht ganz genögt hat: die sollen hier ja, Stück für Stück, die Inhalte der später genutzten HTML-Elemente einsammeln und aufbereiten. Und dann, nach getaner Arbeit, eine Referenz für eine noch folgende Bearbeitung liefern. So eine IIFE läuft also wie eine „normale Funktion“, hat aber ein … hmmm … diverses Innenleben.

        Mit Parameter(n) versehen wird dann vielleicht sogar etwas wie ein Job-Array draus, das nach den Vorarbeiten das Zusammenbacken des HTML steuert.

        Dieser „instrumentierbare Debug“ gefällt mir! Zwar noch nicht ausprobiert, aber auf den Merkzettel kommt der auch!


        Und dann war da noch … („für alle, die’s interessieren mag“)

        In letzter Zeit schreibe ich immer wieder/öfter anstelle von

        for (let x=…; <condition>; }
        

        ein

        for (;;) {
        const x =…;
        if( ! <condition> ) break;
        …
        }
        

        denn sehr oft ist die Variable nur im for in variabler Nutzung. (“give the compiler a chance to tell you about …”) — Hey, bei `for(…in/of…) klappt’s doch auch, das mit dem const 🥸)


        Was ich immer wieder gerne mal hätte (aber nicht weiß, wie das „optisch“ gut aussehen könnte): das Gegenstück zum … hmmm… syntactic sugar for(…){} (für while(){}): ein Zückerchen in der do{}while()-Variante:

        for(A;B;C) { … } entspricht { A; while(B) {…}}

        Und ich hätte gerne ein ??? für { A; do { C } while(B); } Weil, jedenfalls mir, immer wieder Gelegenheiten für so etwas unterkommen:

        {
        …Deklaration… /* v.a. für die Testbedingung im while, auch da möglichst ein const */
        {
        … /* wie sonst bei do/while ja auch */
        } while( … )
        }
        

        Wichtig ist’s nicht, es würde nur gefälliger aussehen. Wenn man denn wüßte, wie es aussehen könnte, wenn damit die Besichtigung des Quelltextes erhellend und nicht verwirrend sein soll.


        Und dann noch syntactic sugar: daß switch auch einer für (bestimmte) if(){}else if(){} else … ist vielen auch nicht bewußt. Dabei kann man sich, hie und da, mit einem switch(true) { case <check>: … } doch so manches Herumgeschwurbel wegen des Formulierens von <test> im „normalen“ switch(<test>) { … } ersparen.

        1. problematische Seite

          Hallo Call the Doctor!,

          normalerweise verwendet man kopf- (while {...}) oder fußgesteuerte Schleifen (do {...} while() ).

          Diese können je nach Logik auch eine zusätzliche Abbruchbedingung mittendrin haben, Puristen würden das aber einen heimlichen goto nennen.

          Wenn in einem Konstrukt wie

          for(;;) {
             // do A1
             if (condition) break;
             // do A2
          }
          

          bei dem "do something" mehr passiert als die Vorbereitung der Bedingung, gilt das hingegen als nicht mehr strukturiert.

          In schlechten alten COBOL-Tagen habe ich gelernt, dass man dann

          VORLAUF
          PERFORM UNTIL CONDITION
             ACTION
             NACHLAUF
          END-PERFORM
          

          programmiert. Wobei Vorlauf und Nachlauf die für Init und Re-Init angepasste Form von A1 darstellen.

          Dein Konstrukt

          for (let x; <condition>; ) {...}
          

          ist, wenn x in der Bedingung nicht genutzt wird, allerdings besser dran, wenn Du es durch while ersetzt. Vor allem, wenn x pro Durchlauf als unverändertlicher Wert bestimmt werden kann. Die let x-Variante im for-Statement hat die Eigenschaft, dass x den Wert aus dem vorigen Durchlauf behält. Das kann Absicht sein oder ein potenzieller Bug. Es hat jedenfalls den Vorteil, dass das Schleifen-x außerhalb der Schleife nicht sichtbar ist.

          Bei for (const x of foo) ist der const unproblematisch, weil die for...of Schleife anders agiert als die for (;;)-Schleife. Bei for...of wird pro Durchlauf ein neuer Wert aus dem iterierten Objekt genommen und als const-Wert bereitgestellt. Bei for (;;) gibt es keine automatisierte Datenbeschaffung.

          for(A;B;C) { … } entspricht { A; while(B) {…}}

          Nein, es entspricht { A; while(B) {…; C}}. C ist die „Re-Initialisierung“

          Aber { A; do { C } while(B); } solltest Du als

          for (A; B;) {
             C;
          }
          

          ausdrücken können, solange A nichts anderes ist als Deklaration und Initialisierung von Variablen. Wie Du schon erkannt hast, ergibt const dort keinen Sinn, und die Lösung hast Du auch gefunden: ziehe das const in den Schleifenrumpf. Das ist vollkommen in Ordnung, finde ich.

          Ein for()-Zückerchen für do {} while gibt's nicht. Das ist schon ein Zückerchen für eine while-Schleife mit einem if(!B) break; am Ende.

          switch(true) als Ersatz für eine if/else Kette ist eigentlich unnötig. Man tippt wegen der ganzen break-Anweisungen deutlich mehr. Die switch-Anweisung ist als optimierbare Auswahl über einen festen Wert gedacht. Wenn Du die case-Labels berechnest, und sei es als booleschen Wert, ist die Optimierung für die Katz. Aus meiner Sicht wäre das eher syntactic vinegar.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. problematische Seite

            Hoppla, hab’ da gerade ein „C“ unter dem Tisch gefunden! 😁

            Und: es ging mir ja „nur“ im ästhetische Aspekte im und um den Quelltext. (Aber die sind doch bestimmt auch Argumente für so manches Stück Sprachzucker gewesen?)

            Bleibt nur die if/switch/else-Frage: doch, ich finde schon, daß da ein swtich() in mancher Hinsicht hilfreich und auch arbeitssparend sein kann. Weil es, wenn es an passenden Stellen verwendet wird, die logische Stuktur deutlicher zeigt als ein „if/else if/if/else if/else if else else … Monster“

            Und: es kann bei der Fehler-Abwehr helfen. Weil die Fallunterscheidung nicht so leicht (versehentlich) getrennt werden kann:

            x = doSth();
            if(x < 0) { … }
            /*else?*/ if(x == 0) { … }
            /*else?*/ if(x > 0) { … }
            

            wehrt sich wenig gegen „Vandalismus“ wie

            x = doSth();
            if(x < 0) { … }
            let y = x;
            x = doSthElse();
            /* da ist nix mit else! */ if(x == 0) { … }
            /*else?*/x = -y;
            if(x > 0) { … }
            

            Natürlich kann man da auch „elsen“. Aber schon schielt womöglich das Monster um die Ecke?