Différences entre les attributs HTML et les propriétés DOM
(jakearchibald.com)- Même lorsqu’ils sont associés au même élément, les attributs HTML et les propriétés DOM fonctionnent comme des espaces de stockage distincts, si bien qu’une modification de l’un n’est pas forcément répercutée automatiquement sur l’autre
- Les attributs apparaissent dans la sérialisation HTML et dans le panneau des éléments des outils de développement, tandis que les propriétés relèvent d’un état propre au DOM et n’apparaissent pas dans des résultats comme
outerHTML - Les valeurs d’attribut sont toujours des chaînes de caractères et leurs noms ne sont pas sensibles à la casse, alors que les propriétés peuvent avoir différents types, y compris des objets, et que leurs noms sont sensibles à la casse
- Il existe des propriétés qui reflètent des attributs comme
id,className,htmlFor,ariaLabel, mais aussi des exceptions où l’état courant et la configuration par défaut sont séparés, commeinput.value - Selon les frameworks, la cible de l’affectation diffère : Preact et VueJS privilégient généralement les propriétés, React privilégie généralement les attributs, et Lit distingue explicitement les deux avec la syntaxe
.value
Les attributs et les propriétés peuvent différer même s’ils portent le même nom
- Les attributs et les propriétés peuvent se comporter comme des espaces de stockage distincts
- Dans ``,
getAttribute('foo')renvoie'bar', alors quediv.foopeut valoirundefined - Même après
div.foo = 'hello world', la valeur de l’attributfooreste'bar'
- Dans ``,
- Même si un template de framework utilise une syntaxe qui ressemble à un attribut, comme ``, il peut en interne définir une propriété selon le cas
- Le choix de définir une propriété dépend du framework
- Certaines affectations peuvent avoir des effets de bord qui modifient à la fois la propriété et l’attribut
Ce qui reste dans la sérialisation HTML, ce sont les attributs
- Les attributs existent à la fois dans le HTML et le DOM, alors que les propriétés n’existent que dans le DOM
- Même après avoir exécuté
div.setAttribute('foo', 'bar')etdiv.hello = 'world', seul l’attribut apparaît dansdiv.outerHTML, comme `
`
- Le panneau des éléments des outils de développement du navigateur affiche les attributs de l’élément, pas l’ensemble de ses propriétés
Type des valeurs et gestion de la casse
- Les valeurs d’attribut doivent fonctionner dans un format sérialisé, elles sont donc toujours des chaînes de caractères
- Si on passe un objet à
setAttribute, il est stocké sous forme de chaîne"[object Object]" - Le même objet affecté à une propriété peut conserver son type objet
- Si on passe un objet à
- Les noms d’attribut ne sont pas sensibles à la casse
HeLlO="world"peut être normalisé en attributhellodans le DOMsetAttribute('FOO', 'bar')apparaît également comme l’attributfoo
- Les noms de propriété, eux, sont sensibles à la casse
- Même après
div.TeSt = 'value',div.testpeut valoirundefined
- Même après
- Contrairement aux noms d’attribut, les valeurs d’attribut sont sensibles à la casse
Les propriétés qui reflètent les attributs
- Certaines propriétés reflètent un attribut donné
div.idagit comme un getter/setter qui lit et écrit l’attributid- Si on exécute
div.id = 'bar', l’attributidpasse lui aussi à'bar'
- Dans une relation de réflexion, c’est l’attribut qui constitue la source de vérité
- Définir la propriété met à jour l’attribut
- Lire la propriété revient à lire la valeur de l’attribut
- Les attributs définis dans les spécifications disposent souvent d’une propriété correspondante par commodité
- Un attribut arbitraire
foon’étant pas défini par une spécification, une propriétéfoocorrespondante n’est pas créée automatiquement
- Un attribut arbitraire
- La spécification de `` définit les attributs dans “Content attributes” et les propriétés dans “DOM interface”
- Les propriétés IDL
reversedettypedoivent refléter les attributs de contenu du même nom
- Les propriétés IDL
Quand le nom change dans la relation de réflexion
- Certaines propriétés ont un nom différent de celui de l’attribut qu’elles reflètent
- Ce changement peut servir à mieux correspondre au style des propriétés JavaScript
- Sur ``,
el.crossOriginreflète l’attributcrossorigin - Sur tous les éléments,
el.ariaLabelreflète l’attributaria-label - Les propriétés de réflexion ARIA sont devenues compatibles tous navigateurs fin 2023 ; avant cela, seuls les attributs pouvaient être utilisés
- Sur ``,
- Dans d’autres cas, le nom diffère à cause d’anciens mots réservés de JavaScript
- Sur tous les éléments,
el.classNamereflète l’attributclass - Sur ``,
el.htmlForreflète l’attributfor
- Sur tous les éléments,
Validation, conversion de type et valeurs par défaut
- Les propriétés peuvent intégrer une validation et des valeurs par défaut, contrairement aux attributs eux-mêmes
input.typeillustre bien la différence entre attribut et propriété- Même si
input.getAttribute('type')vautnull,input.typerenvoie la valeur par défaut'text' input.type = 'number'donne'number'côté attribut comme côté propriétéinput.type = 'foo'écrit'foo'dans l’attribut, mais le getter de la propriété considère cette valeur comme invalide et renvoie'text'
- Même si
- Certaines propriétés effectuent une conversion de type
- Sur ``,
details.getAttribute('open')est une chaîne vide, alors quedetails.openvauttrue details.open = falsesupprime l’attributopendetails.open = 'hello'n’écrit pas la chaîne telle quelle dans l’attribut : la valeur est convertie en booléen, ce qui laisse l’attributopenprésent
- Sur ``,
- Des propriétés comme
img.heightconvertissent la valeur de l’attribut en nombre- Le setter convertit la valeur reçue en nombre et traite les valeurs négatives comme
0
- Le setter convertit la valeur reçue en nombre et traite les valeurs négatives comme
input.value sépare la valeur courante et la valeur par défaut
inputpossède à la fois une propriétévalueet un attributvalue, mais la propriétévaluene reflète pas l’attributvalue- La propriété qui reflète l’attribut
valueestdefaultValue- Le getter de
defaultValuerenvoie l’attributvalueou une chaîne vide - Le setter de
defaultValuedéfinit l’attributvaluesous forme de chaîne
- Le getter de
- La propriété
valuesuit d’aborddefaultValue, puis utilise une valeur interne dès qu’elle est définie par JavaScript ou par une interaction utilisateur- Au départ, dans ``,
value,defaultValueetgetAttribute('value')valent tous'default' - Après
input.defaultValue = 'new default', l’attribut etvaluesemblent tous deux valoir'new default' - Ensuite, après
input.value = 'hello!',valuedevient'hello!', tandis que l’attribut etdefaultValuerestent à'new default' - Même après
setAttribute('value', 'another new default'), la valeur courante devaluereste'hello!'
- Au départ, dans ``,
- Lors d’une réinitialisation du formulaire, l’état interne de
valuepeut être réinitialisé
Les attributs conviennent mieux à la configuration, les propriétés à l’état
- Il est naturel de réserver les attributs à la configuration et les propriétés à l’état
- `` en est un bon exemple, sauf pour le nommage
- L’attribut
valuedéfinit la valeur par défaut - La propriété
valuefournit l’état courant
- L’attribut
- La distinction selon laquelle la validation s’applique lors de la lecture ou de l’écriture d’une propriété, mais pas lors de la lecture ou de l’écriture d’un attribut, va dans le même sens
etexpriment leur état ouvert avec l’attributopen, que le navigateur peut lui-même ajouter ou supprimer selon les interactions utilisateur- Cette conception brouille la distinction selon laquelle les attributs servent à la configuration
- Les frameworks qui maintiennent le DOM, comme le JavaScript classique, doivent donc gérer le fait que le DOM puisse se modifier lui-même
- Une autre conception pourrait séparer un attribut de configuration par défaut comme ``, une propriété
details.openpour lire et écrire l’état courant, et une pseudo-classe CSS pour cibler cet état - Simon Peters a retrouvé une discussion de conception initiale à ce sujet
contenteditablebrouille lui aussi cette distinction, mais il est traité comme un mécanisme opt-in qui accepte beaucoup de comportements cassants
Approche selon les frameworks
- Preact et VueJS définissent une propriété lorsque
propName in elementest vrai, sauf pour quelques exceptions prédéfinies ; sinon, ils définissent un attribut- Ils privilégient donc les propriétés plutôt que les attributs
- Les méthodes de rendu en chaîne de caractères font l’inverse et ignorent les éléments réservés aux propriétés
- Implémentations associées :
setPropertyde Preact,shouldSetAsPropde VueJS
- React définit généralement des attributs, sauf pour certaines exceptions prédéfinies
- Cette approche produit une logique proche de celle du rendu en chaîne de caractères
- Les propriétés des éléments personnalisés ne figurant pas dans la liste prédéfinie de React, elles sont définies comme attributs, et les API réservées aux propriétés peuvent ne pas fonctionner
- Avec React 19, les éléments personnalisés doivent adopter le modèle de Preact/VueJS
- React a popularisé le nom de propriété
classNamedans une syntaxe qui ressemble à un attribut, mais en interne il définit l’attributclass - Implémentation associée :
setPropde React
- lit-html conserve la différence entre attributs et propriétés dans sa syntaxe
- En préfixant le nom par
., comme dans ``, on définit une propriété et non un attribut - Documentation associée : Lit expressions
- En préfixant le nom par
3 commentaires
Avis sur Hacker News
Utiliser les attributs
data-*constitue un bon compromis entre les approches abordées dans l’articleCes attributs sont automatiquement exposés en JavaScript comme propriétés en lecture/écriture de
dataset, et sont donc accessibles sous une forme commedocument.getElementById('myDiv').dataset.payload == "something"Il faut toutefois faire attention au fait que les attributs HTML sont en
kebab-case, tandis que JavaScript utilise lecamelCase, si bien quedata-my-cool-data-attributedevientdataset.myCoolDataAttributeDans les différents écosystèmes qui utilisent le
camelCase, il n’existe pas de convention bien établie sur la manière de traiter les acronymes en majuscules, ce qui rend des cas commedataset.myIDambiguskebab-caseetcamelCase, j’utilise des méthodes commeElement.hasAttribute('data-thing'),Element.getAttribute('data-thing'),Element.setAttribute('data-thing', '...')Quand on inspecte ou débogue le DOM, cela évite de passer par une conversion maladroite et rend l’intention beaucoup plus claire
Par ailleurs, https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att... décrit les attributs globaux
data-*comme des « attributs de données personnalisés qui permettent aux scripts d’échanger des informations propres entre HTML et sa représentation DOM »Par contexte, il ne s’agit pas forcément de différents
iframe, mais aussi de cas comme les extensions de navigateur, qui créent un contexte d’exécution JavaScript séparé accédant au même DOMDans la terminologie du protocole des outils de développement, on appelle aussi cela des isolated worlds
Ce dont parle l’article illustre une complexité et des comportements qui n’apportent pas grand-chose dans la vie quotidienne
Ces comportements n’ont que des inconvénients et aucun avantage ; la spécification aurait dû simplement définir les objets DOM et les objets JavaScript comme étant la même chose, et faire des attributs et des propriétés un seul et même concept
Le fait de pouvoir stocker des valeurs qui ne sont pas des chaînes sur des nœuds DOM est un avantage évident, et ce n’est pas un point à balayer d’un revers de main
Ce n’est pas simple du tout, sauf à exiger que toutes les propriétés ne puissent être que des chaînes
Il faut aussi prendre en compte les conversions avec les nombres, les propriétés pouvant recevoir des objets, et les propriétés qui ne correspondent à aucun attribut
Cela dit, il aurait été préférable d’avoir une convention ou une règle figée à 100 %, mais ce n’est pas une chose simple
Des méthodes comme
addEventListenersont des propriétés du prototype des éléments DOM individuels, mais vues comme des attributs textuels, cela n’a aucun sensLes méthodes des éléments DOM devraient alors devenir des fonctions libres comme
HTMLElement.addEventListener(foo, 'some-event', e => console.log(e));, et même si c’est une question de préférence, il est appréciable de pouvoir utiliser en JavaScript des appels de méthodes sur des objetsLes éléments DOM disposent d’une interface très spécifique,
setAttribute, pour modifier leur état, et je ne vois pas pourquoi on devrait s’attendre à ce qu’une propriété ajoutée directement à l’objet JavaScript conçu pour envoyer des messages au DOM soit automatiquement routée vers cette interfaceUne telle API pourrait offrir une meilleure expérience, mais elle soulève quelques difficultés, même si elle serait sans doute plus propre
Peut-être est-ce parce que j’ai écrit du code backend pendant trop longtemps, mais face à ce type d’interface, j’ai l’impression qu’il se passe beaucoup de choses étranges en interne, et qu’un gentil développeur du passé a fourni le wrapper le plus sûr possible
Un point de vue utile pour comprendre est qu’au sens strict, un attribut est un nœud DOM, c’est-à-dire un nœud
AttrCe nœud peut avoir des propriétés comme
name,value,ownerElement,prefix, et l’ensemble est accessible non pas sous forme de tableau, mais sous forme deNamedNodeMap, via la propriétéattributesde l’élément propriétaireLe nom
getAttribute(name)est donc trompeur : en réalité, il ne renvoie pas le nœud d’attribut lui-même, mais la propriétévaluede ce nœud, ce qui le rapproche davantage degetAttributeValue(name)Références : 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
setAttribute,getAttribute,getAttributeNames,hasAttribute, et qui nécessite les nœuds d’attribut ouel.attributesOn ne peut pas non plus déplacer des nœuds d’attribut entre éléments, ni changer l’ordre des nœuds d’attribut au sein d’un élément
Jake, alors on fait quoi de ça : https://jakearchibald.com/2023/against-self-closing-tags-in-...
Je comprends l’argument consistant à ne pas mettre à jour les propriétés, mais le fait de pouvoir cibler ces éléments avec du CSS est utile
:checkedpour les éléments de saisieIl est beaucoup plus facile de déboguer en inspectant immédiatement le DOM qu’en passant par le débogueur
La différence la plus importante est probablement la persistance, mais l’article n’en parle pas
Les attributs sont conservés tant qu’on peut accéder au même objet racine de cet arbre DOM, par exemple tant qu’on ne quitte pas la page
Les propriétés d’objet ne sont conservées que jusqu’à ce que cet objet soit collecté par le garbage collector
Si le nœud DOM est cloné, ou s’il passe par une sérialisation/désérialisation, il devient un nœud DOM complètement différent avec les mêmes attributs, et n’a donc pas les propriétés non par défaut de l’autre nœud
Article intéressant
J’ai toujours vu les éléments DOM en JavaScript comme des proxys
Ils permettent de travailler avec les vrais éléments DOM, mais comme ce sont aussi simplement des objets JavaScript, j’avais l’impression qu’en plus d’utiliser les fonctions qui interagissent avec les éléments réels, on pouvait leur assigner des valeurs et en faire ce qu’on voulait
C’est agréable à lire, bien expliqué avec des exemples, et cela devrait aider beaucoup de monde, surtout avec plusieurs détails qu’on oublie souvent
Les propriétés DOM sont aussi appelées IDL attributes
En dehors des auteurs de spécifications, peu de gens disent
IDL attributes, et si on appelle les deux « attributs », cela devient très confusBon article, et je me demande à quoi pourrait ressembler une meilleure façon d’exprimer tout cela
Il serait intéressant que le HTML puisse avoir des attributs qui ne soient pas des chaînes de caractères, et dont la valeur référence des valeurs locales d’une balise
scriptavec portée, par exempleLa façon de voir les attributs comme des réglages par défaut m’a semblé un peu étrange
Par exemple, si l’attribut
classne montrait que la valeur par défaut, alors lorsqu’on bascule des classes, ce serait assez bizarre de ne pas voir les classes actives, ou de voir des classes inactives, dans l’inspecteur DOM ou dans le HTML converti en chaîneL’attribut
classfonctionne bien dans ce modèleEn tant que propriétaire du document, je définis l’attribut
class, et je peux modifier ce réglage au fil du temps, mais je suis le seul à le faireDans un autre modèle, le navigateur ajouterait et retirerait lui-même des classes en réaction aux états hover ou focus ; heureusement, il ne le fait pas, et on utilise des pseudo-classes
https://emewjin.github.io/html-attribute-dom-property/
Ils ont traduit à la fois attribute et property par le même mot, « attribut »...