6 Punkte von GN⁺ 2024-04-27 | 3 Kommentare | Auf WhatsApp teilen
  • Auch wenn sie am selben Element hängen, verhalten sich HTML-Attribute und DOM-Properties wie getrennte Speicherbereiche; eine Änderung auf der einen Seite wird nicht unbedingt automatisch auf die andere übertragen
  • Attribute erscheinen in der HTML-Serialisierung und im Elemente-Panel der Entwicklertools, Properties sind dagegen DOM-interner Zustand und tauchen in Ergebnissen wie outerHTML nicht auf
  • Attributwerte sind immer Strings, und ihre Namen sind nicht case-sensitiv; Properties können hingegen verschiedene Typen wie Objekte haben, und ihre Namen sind case-sensitiv
  • Während es Properties gibt, die Attribute reflektieren, etwa id, className, htmlFor oder ariaLabel, gibt es auch Ausnahmen wie input.value, bei denen aktueller Zustand und Voreinstellung getrennt sind
  • Je nach Framework wird Unterschiedliches gesetzt: Preact und VueJS setzen meist Properties, React meist Attribute, und Lit unterscheidet beides explizit mit der .value-Syntax

Attribute und Properties können trotz gleichen Namens verschieden sein

  • Attribute und Properties können sich wie unterschiedliche Speicherbereiche verhalten
    • Bei `` gibt getAttribute('foo') den Wert 'bar' zurück, während div.foo undefined sein kann
    • Selbst wenn danach div.foo = 'hello world' gesetzt wird, bleibt der Wert des foo-Attributs 'bar'
  • Auch wenn in Framework-Templates eine Syntax verwendet wird, die wie ein Attribut aussieht, etwa ``, kann intern je nach Fall eine Property gesetzt werden
    • Wann eine Property gesetzt wird, unterscheidet sich je nach Framework
    • Manche Zuweisungen können als Nebeneffekt sowohl Property als auch Attribut verändern

In der HTML-Serialisierung bleiben Attribute erhalten

  • Attribute existieren sowohl in HTML als auch im DOM, Properties dagegen nur im DOM
  • Selbst wenn sowohl div.setAttribute('foo', 'bar') als auch div.hello = 'world' ausgeführt werden, erscheinen in div.outerHTML nur Attribute, etwa `

`

  • Das Elemente-Panel der Browser-Entwicklertools zeigt die Attribute eines Elements und nicht die Gesamtheit seiner Properties

Werttypen und Groß-/Kleinschreibung

  • Attributwerte müssen in serialisierter Form funktionieren und sind deshalb immer Strings
    • Wenn ein Objekt mit setAttribute gesetzt wird, wird es als String "[object Object]" gespeichert
    • Wird dasselbe Objekt in eine Property geschrieben, kann der Objekttyp unverändert erhalten bleiben
  • Attributnamen sind nicht case-sensitiv
    • HeLlO="world" kann im DOM zum Attribut hello normalisiert werden
    • Auch setAttribute('FOO', 'bar') erscheint als Attribut foo
  • Property-Namen sind case-sensitiv
    • Selbst wenn div.TeSt = 'value' gesetzt wird, kann div.test undefined sein
  • Anders als Attributnamen sind Attributwerte case-sensitiv

Properties, die Attribute reflektieren

  • Einige Properties reflektieren bestimmte Attribute
    • div.id verhält sich als Getter/Setter, der das Attribut id liest und schreibt
    • Wird div.id = 'bar' ausgeführt, ändert sich auch das Attribut id zu 'bar'
  • In einer Reflexionsbeziehung ist das Attribut die Datenquelle
    • Das Setzen der Property aktualisiert das Attribut
    • Das Lesen der Property liest den Attributwert
  • Für in der Spezifikation definierte Attribute werden zur Bequemlichkeit häufig entsprechende Properties angelegt
    • Ein beliebiges Attribut foo ist kein in der Spezifikation definiertes Attribut, daher entsteht nicht automatisch eine entsprechende Property foo
  • Die ``-Spezifikation definiert Attribute unter „Content attributes“ und Properties unter „DOM interface“
    • Die IDL-Attribute reversed und type müssen die gleichnamigen Content-Attribute reflektieren

Reflexionsbeziehungen mit abweichenden Namen

  • Manche Properties haben einen anderen Namen als das Attribut, das sie reflektieren
  • Teils weicht der Name ab, um zum Stil von JavaScript-Properties zu passen
    • el.crossOrigin von `` reflektiert das Attribut crossorigin
    • el.ariaLabel aller Elemente reflektiert das Attribut aria-label
    • ARIA-Reflexions-Properties wurden Ende 2023 browserübergreifend verfügbar; davor konnten nur die Attribute verwendet werden
  • In anderen Fällen änderte sich der Name wegen früherer reservierter Wörter in JavaScript
    • el.className aller Elemente reflektiert das Attribut class
    • el.htmlFor von `` reflektiert das Attribut for

Validierung, Typumwandlung und Standardwerte

  • Properties können Validierung und Standardwerte haben, Attribute selbst dagegen nicht
  • input.type zeigt den Unterschied zwischen Attributen und Properties gut
    • Selbst wenn input.getAttribute('type') null ist, gibt input.type den Standardwert 'text' zurück
    • input.type = 'number' sieht sowohl im Attribut als auch in der Property wie 'number' aus
    • input.type = 'foo' schreibt zwar 'foo' ins Attribut, der Property-Getter erkennt aber den ungültigen Wert und gibt 'text' zurück
  • Einige Properties führen Typumwandlungen aus
    • Bei `` ist details.getAttribute('open') ein leerer String und details.open ist true
    • details.open = false entfernt das Attribut open
    • details.open = 'hello' schreibt den String nicht unverändert ins Attribut, sondern wandelt ihn in einen Boolean um, sodass das Attribut open vorhanden ist
  • Properties wie img.height wandeln Attributwerte in Zahlen um
    • Der Setter wandelt den eingehenden Wert in eine Zahl um; negative Werte werden als 0 behandelt

input.value trennt aktuellen Wert und Standardwert

  • Bei input gibt es eine value-Property und ein value-Attribut, aber die value-Property reflektiert das value-Attribut nicht
  • Die Property, die das value-Attribut reflektiert, ist defaultValue
    • Der defaultValue-Getter gibt das value-Attribut oder einen leeren String zurück
    • Der defaultValue-Setter setzt das value-Attribut als String
  • Die value-Property folgt zunächst defaultValue, verwendet aber einen internen Wert, sobald sie durch JavaScript oder Benutzerinteraktion gesetzt wurde
    • Bei anfänglichem `` sind value, defaultValue und getAttribute('value') alle 'default'
    • Wird input.defaultValue = 'new default' ausgeführt, sehen sowohl Attribut als auch value wie 'new default' aus
    • Wird danach input.value = 'hello!' ausgeführt, wird value zu 'hello!', während Attribut und defaultValue bei 'new default' bleiben
    • Auch wenn setAttribute('value', 'another new default') ausgeführt wird, bleibt der aktuelle value bei 'hello!'
  • Wird das Formular zurückgesetzt, kann dies so funktionieren, dass der interne value-Zustand initialisiert wird

Attribute passen natürlicher zu Konfiguration, Properties zu Zustand

  • Die Unterscheidung, Attribute für Konfiguration und Properties für Zustand zu verwenden, ist natürlich
  • `` ist, abgesehen vom Namen, ein Beispiel, das zu dieser Unterscheidung passt
    • Das value-Attribut legt den Standardwert fest
    • Die value-Property stellt den aktuellen Zustand bereit
  • Auch die Unterscheidung, dass beim Lesen oder Schreiben von Properties Validierung angewendet wird, beim Lesen oder Schreiben von Attributen jedoch nicht, passt zu diesem Modell
  • und drücken ihren geöffneten Zustand mit dem Attribut open aus, und der Browser fügt dieses Attribut je nach Benutzerinteraktion direkt hinzu oder entfernt es
    • Dieses Design durchbricht die Unterscheidung, dass Attribute für Konfiguration gedacht sind
    • Frameworks, die das DOM verwalten, oder gewöhnlicher JavaScript-Code müssen auf Situationen vorbereitet sein, in denen sich das DOM selbst ändert
  • Ein alternatives Design könnte in ein Attribut für die Voreinstellung wie ``, eine Property details.open zum Lesen und Schreiben des aktuellen Zustands und eine CSS-Pseudoklasse für Zustands-Targeting aufgeteilt sein
  • Simon Peters hat eine dazugehörige frühe Designdiskussion gefunden
  • Auch contenteditable durchbricht diese Unterscheidung, wird aber als Opt-in behandelt, bei dem viele Brüche in Kauf genommen werden

Verarbeitung in verschiedenen Frameworks

  • Preact und VueJS setzen, abgesehen von einigen vordefinierten Ausnahmen, eine Property, wenn propName in element wahr ist; andernfalls setzen sie ein Attribut
    • Standardmäßig bevorzugen sie Properties gegenüber Attributen
    • String-Rendering-Methoden verhalten sich umgekehrt und ignorieren rein Property-basierte Einträge
    • Zugehörige Implementierungen: setProperty in Preact, shouldSetAsProp in VueJS
  • React setzt, abgesehen von einigen vordefinierten Ausnahmen, Attribute
    • Dieser Ansatz führt zu einer Logik, die String-Rendering-Methoden ähnelt
    • Properties von Custom Elements stehen nicht in Reacts vordefinierter Liste, werden daher als Attribute gesetzt, und rein Property-basierte APIs funktionieren unter Umständen nicht
    • In React 19 soll für Custom Elements auf das Preact/VueJS-Modell umgestellt werden
    • React hat den Property-Namen className in einer attributähnlichen Syntax populär gemacht, setzt intern aber das class-Attribut
    • Zugehörige Implementierung: setProp in React
  • lit-html hält den Unterschied zwischen Attributen und Properties in der Syntax fest
    • Wenn dem Namen wie in `` ein . vorangestellt wird, wird nicht ein Attribut, sondern eine Property gesetzt
    • Zugehörige Dokumentation: Lit expressions

3 Kommentare

 
GN⁺ 2024-04-27
Hacker-News-Meinungen
  • data-*-Attribute sind ein guter Kompromiss zwischen den im Artikel behandelten Ansätzen.
    Diese Attribute werden in JavaScript automatisch als les-/schreibbare Eigenschaften von dataset verfügbar gemacht und können etwa so angesprochen werden: document.getElementById('myDiv').dataset.payload == "something".
    Man muss allerdings beachten, dass HTML-Attribute kebab-case verwenden, JavaScript dagegen camelCase; aus data-my-cool-data-attribute wird also dataset.myCoolDataAttribute.

    • Die automatische Umwandlung der Groß-/Kleinschreibung finde ich wirklich furchtbar.
      In den verschiedenen Ökosystemen, die camelCase verwenden, gibt es keine etablierte Konvention dafür, wie Akronyme in Großbuchstaben behandelt werden sollen; Fälle wie dataset.myID werden dadurch uneindeutig.
    • Um die Umwandlung zwischen kebab-case und camelCase zu vermeiden, nutze ich Methoden wie Element.hasAttribute('data-thing'), Element.getAttribute('data-thing') und Element.setAttribute('data-thing', '...').
      Beim Prüfen oder Debuggen im DOM muss man dann keine merkwürdige Umwandlung mitdenken; die Absicht ist deutlich klarer.
    • Die MDN-Dokumentation zu data attributes behandelt HTML-Syntax, Zugriff aus JavaScript, Zugriff aus CSS sowie Hinweise zur Barrierefreiheit: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Us...
      Außerdem beschreibt https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att... das globale Attribut data-* als „benutzerdefinierte Datenattribute, die es Skripten ermöglichen, eigene Informationen zwischen HTML und der DOM-Darstellung auszutauschen“.
    • Diese Attribute sind bis zu einem gewissen Grad auch aus CSS zugänglich.
    • Sie sind außerdem eine interessante weitere Möglichkeit, zwischen JavaScript-Ausführungskontexten zu kommunizieren.
      Mit Kontexten sind hier nicht unbedingt verschiedene iframes gemeint, sondern Fälle, in denen — etwa bei Browser-Erweiterungen — separate JavaScript-Ausführungskontexte entstehen, die auf dasselbe DOM zugreifen.
      In der Terminologie des Developer-Tools-Protokolls nennt man so etwas auch isolated worlds.
  • Die im Artikel behandelten Dinge stehen für Komplexität und Verhalten, die im Leben kaum hilfreich sind.
    Dieses Verhalten hat keine Vorteile, nur Nachteile; die Spezifikation hätte DOM-Objekte und JavaScript-Objekte einfach als dasselbe definieren und Attribute und Properties ebenfalls zum selben Konzept machen sollen.

    • Wenn es diese Einschränkung „einfach“ gegeben hätte, würden die meisten modernen UI-Frameworks überhaupt nicht funktionieren.
      Dass man auf DOM-Knoten nicht-String-Werte speichern kann, ist ein klarer Vorteil und nichts, was man zu leicht abtun sollte.
    • Abgesehen von dem „einfach“ stimme ich zu.
      Es ist überhaupt nicht einfach, solange man nicht verlangt, dass alle Properties nur Strings sein dürfen.
      Man muss Konvertierungen von und zu Zahlen, Properties, denen Objekte zugewiesen werden können, und Properties ohne entsprechendes Attribut berücksichtigen.
      Trotzdem wäre es besser gewesen, wenn es zu 100 % feste Konventionen oder Regeln gegeben hätte — aber einfach ist das nicht.
    • So könnte man in DOM-Interfaces keine nicht-String-Werte unterbringen, und da auch Methoden dazugehören, würde der Großteil der DOM-Funktionalität verschwinden.
    • Attribute und Properties symmetrisch zueinander zu machen, ist mit der bestehenden DOM-API schwierig.
      Methoden wie addEventListener sind Properties auf dem Prototyp einzelner DOM-Elemente, ergeben als String-Attribute aber keinen Sinn.
      Dann müssten DOM-Elementmethoden freie Funktionen wie HTMLElement.addEventListener(foo, 'some-event', e => console.log(e)); sein; das ist zwar Geschmackssache, aber in JavaScript ist es angenehmer, Objektmethodenaufrufe verwenden zu können.
    • Ich verstehe nicht ganz, warum die beiden unterschiedlich sein müssen.
      DOM-Elemente haben mit setAttribute ein sehr konkretes Interface, um Zustand zu ändern; wenn man an das JavaScript-Objekt, das dazu gedacht ist, Nachrichten an das DOM zu senden, einfach Properties anhängt, warum sollte man dann erwarten, dass das automatisch weitergeleitet wird?
      Eine solche API könnte eine bessere Erfahrung bieten, bringt aber einige schwierige Fragen mit sich; trotzdem wäre sie wohl sauberer.
      Vielleicht liegt es daran, dass ich zu lange Backend-Code geschrieben habe, aber bei solchen Interfaces wirkt es auf mich, als passiere intern eine Menge Seltsames und als hätte ein freundlicher Entwickler aus der Vergangenheit den sichersten möglichen Wrapper bereitgestellt.
  • Eine hilfreiche Perspektive für das Verständnis ist, dass Attribute im engeren Sinne DOM-Knoten sind, nämlich Attr-Knoten.
    Ein solcher Knoten kann Properties wie name, value, ownerElement und prefix haben und ist insgesamt nicht als Array, sondern als NamedNodeMap über die Property attributes des besitzenden Elements zugänglich.
    Daher ist der Name getAttribute(name) irreführend; tatsächlich gibt die Methode nicht den Attributknoten selbst zurück, sondern die Property value dieses Attributknotens, entspricht also eher getAttributeValue(name).
    Siehe: https://developer.mozilla.org/en-US/docs/Web/API/Element/get..., https://developer.mozilla.org/en-US/docs/Web/API/Element/get..., https://developer.mozilla.org/en-US/docs/Web/API/Attr

    • Diese Erklärung stimmt, aber ich weiß nicht, ob es irgendetwas Praktisches gibt, das man nicht mit setAttribute, getAttribute, getAttributeNames oder hasAttribute, sondern nur über Attributknoten oder el.attributes tun kann.
      Man kann Attributknoten weder zwischen Elementen verschieben noch die Reihenfolge der Attributknoten innerhalb eines Elements ändern.
  • Jake, was ist dann damit: https://jakearchibald.com/2023/against-self-closing-tags-in-...

    • „Ihr werdet diese Syntax in meinem Blog sehen, weil Prettier das so macht, und ich mag Prettier wirklich sehr.“
  • Ich verstehe das Argument, Properties nicht zu aktualisieren, aber dass man sie per CSS gezielt ansprechen kann, ist nützlich

    • Deshalb wurde gesagt, dass es ähnlich wie :checked bei Eingabeelementen auch Pseudoklassen geben sollte
    • Für mich ist dieser Fall ebenfalls wichtiger als alle anderen Gründe
      Es ist viel einfacher zu debuggen, den DOM direkt zu überprüfen, als durch den Debugger zu gehen
  • Der vielleicht wichtigste Unterschied ist Persistenz, die im Artikel aber nicht behandelt wird
    Attribute bleiben erhalten, solange man auf dasselbe Root-Objekt dieses DOM-Baums zugreifen kann, etwa solange man die Seite nicht verlässt
    Objekt-Properties bleiben nur erhalten, bis das betreffende Objekt von der Garbage Collection erfasst wird

    • Das Objekt existiert, solange der DOM-Knoten mit den Attributen existiert, daher ist der Lebenszyklus derselbe
      Wird der DOM-Knoten geklont oder serialisiert/deserialisiert, entsteht ein vollständig anderer DOM-Knoten mit denselben Attributen; er hat dann keine nicht standardmäßigen Properties eines anderen Knotens
    • Hier gibt es abgesehen vom bestehenden Unterschied zwischen Attributen und Properties keinen praktischen Verhaltensunterschied
    • Ich frage mich, wie sich dieser Unterschied in der Praxis zeigt
  • Interessanter Artikel
    Ich habe DOM-Elemente in JavaScript immer als eine Art Proxy gesehen
    Sie lassen einen mit den eigentlichen DOM-Elementen arbeiten, sind aber gleichzeitig einfach JavaScript-Objekte; daher hatte ich das Gefühl, man könne neben den Funktionen, die mit dem echten Element interagieren, auch Werte zuweisen und tun, was man möchte

  • Mit Beispielen gut erklärt und angenehm zu lesen, besonders weil mehrere Details dabei sind, die leicht übersehen werden; das dürfte vielen helfen

  • DOM-Properties werden auch IDL attributes genannt

    • Stimmt, aber ich habe den Begriff absichtlich vermieden
      Außerhalb von Spezifikationsautoren nennt sie kaum jemand IDL attributes, und wenn man beides Attribute nennt, wird es sehr verwirrend
  • Guter Artikel, und er macht neugierig, wie eine bessere Ausdrucksweise aussehen könnte
    Es wäre interessant, wenn HTML nicht-stringbasierte Attribute haben könnte und deren Werte etwa auf lokale Werte eines script-Tags mit Scope verweisen würden
    Die Sichtweise, Attribute als Grundeinstellungen zu sehen, fand ich etwas seltsam
    Wenn zum Beispiel das class-Attribut nur den Standardwert zeigen würde, wäre es ziemlich merkwürdig, wenn beim Umschalten von Klassen die aktive Klasse im DOM-Inspektor oder im stringifizierten HTML nicht sichtbar wäre oder eine inaktive Klasse angezeigt würde

    • Es wurde nicht von Grundeinstellungen gesprochen, sondern einfach von Einstellungen
      Das class-Attribut funktioniert in diesem System gut
      Ich als Eigentümer des Dokuments setze das class-Attribut und kann diese Einstellung im Laufe der Zeit ändern, aber nur ich tue das
      In einem alternativen System würde der Browser als Reaktion auf Hover- oder Focus-Zustände selbst Klassen hinzufügen und entfernen; glücklicherweise macht er das nicht, sondern es werden Pseudoklassen verwendet
 
superwoou 2024-04-27

„Attribute“ und „Property“ wurden beide einfach als „Eigenschaft“ übersetzt …