Diferencias entre atributos de HTML y propiedades del DOM
(jakearchibald.com)- Aunque estén en el mismo elemento, los atributos de HTML y las propiedades del DOM pueden funcionar como espacios de almacenamiento separados, por lo que un cambio en uno no siempre se refleja automáticamente en el otro.
- Los atributos aparecen en la serialización HTML y en el panel de elementos de las herramientas de desarrollo, pero las propiedades son estado exclusivo del DOM, así que no aparecen en resultados como
outerHTML. - Los valores de los atributos siempre son cadenas y sus nombres no distinguen entre mayúsculas y minúsculas, mientras que las propiedades pueden tener distintos tipos, incluidos objetos, y sus nombres sí distinguen mayúsculas de minúsculas.
- Hay propiedades que reflejan atributos, como
id,className,htmlForyariaLabel, pero también hay excepciones comoinput.value, donde el estado actual y la configuración predeterminada están separados. - Cada framework apunta a cosas distintas: Preact y VueJS suelen priorizar propiedades, React suele priorizar atributos, y Lit los distingue explícitamente con la sintaxis
.value.
Atributos y propiedades pueden ser distintos aunque tengan el mismo nombre
- Los atributos y las propiedades pueden funcionar como si fueran espacios de almacenamiento distintos.
- En ``,
getAttribute('foo')devuelve'bar', perodiv.foopuede serundefined. - Después, aunque se haga
div.foo = 'hello world', el valor del atributofoosigue siendo'bar'.
- En ``,
- En las plantillas de los frameworks, aunque se use una sintaxis que parece atributo, como ``, internamente en algunos casos se puede estar configurando una propiedad.
- Qué casos terminan configurando una propiedad depende de cada framework.
- Algunas configuraciones pueden tener efectos secundarios y cambiar tanto la propiedad como el atributo.
Lo que queda en la serialización HTML son los atributos
- Los atributos existen tanto en HTML como en el DOM, pero las propiedades existen solo en el DOM.
- Aunque se ejecuten
div.setAttribute('foo', 'bar')ydiv.hello = 'world', endiv.outerHTMLsolo aparecen los atributos, como en `
`.
- El panel de elementos de las herramientas de desarrollo del navegador muestra los atributos del elemento, no todas sus propiedades.
Tipo de valor y manejo de mayúsculas/minúsculas
- Los valores de los atributos siempre son cadenas porque deben funcionar en un formato serializado.
- Si se pasa un objeto a
setAttribute, se guarda como la cadena"[object Object]". - Si ese mismo objeto se asigna a una propiedad, puede conservarse como objeto.
- Si se pasa un objeto a
- Los nombres de atributos no distinguen mayúsculas y minúsculas.
HeLlO="world"puede normalizarse como el atributohelloen el DOM.setAttribute('FOO', 'bar')también aparece como el atributofoo.
- Los nombres de propiedades sí distinguen mayúsculas y minúsculas.
- Aunque se haga
div.TeSt = 'value',div.testpuede serundefined.
- Aunque se haga
- A diferencia del nombre del atributo, el valor del atributo sí distingue mayúsculas y minúsculas.
Propiedades que reflejan atributos
- Algunas propiedades reflejan un atributo específico.
div.idfunciona como un getter/setter que lee y escribe el atributoid.- Si se ejecuta
div.id = 'bar', el atributoidtambién cambia a'bar'.
- En una relación de reflejo, el atributo es la fuente de verdad.
- Al asignar la propiedad, se actualiza el atributo.
- Al leer la propiedad, se lee el valor del atributo.
- Los atributos definidos en la especificación suelen tener una propiedad correspondiente por conveniencia.
- Un atributo arbitrario como
foono está definido en la especificación, así que no se crea automáticamente una propiedadfoocorrespondiente.
- Un atributo arbitrario como
- La especificación de `` define los atributos en “Content attributes” y las propiedades en “DOM interface”.
- Las propiedades IDL
reversedytypedeben reflejar los atributos de contenido con el mismo nombre.
- Las propiedades IDL
Relaciones de reflejo donde cambia el nombre
- Algunas propiedades tienen un nombre distinto al del atributo que reflejan.
- A veces el nombre cambia para ajustarse al estilo de propiedades de JavaScript.
- En ``,
el.crossOriginrefleja el atributocrossorigin. - En cualquier elemento,
el.ariaLabelrefleja el atributoaria-label. - Las propiedades de reflejo de ARIA se volvieron multiplataforma entre navegadores a finales de 2023; antes de eso solo podían usarse atributos.
- En ``,
- En otros casos, el nombre cambió por antiguas palabras reservadas de JavaScript.
- En cualquier elemento,
el.classNamerefleja el atributoclass. - En ``,
el.htmlForrefleja el atributofor.
- En cualquier elemento,
Validación, conversión de tipo y valores predeterminados
- Las propiedades pueden tener validación y valores predeterminados, pero los atributos por sí solos no tienen ese comportamiento.
input.typemuestra muy bien la diferencia entre atributo y propiedad.- Aunque
input.getAttribute('type')seanull,input.typedevuelve el valor predeterminado'text'. input.type = 'number'se ve como'number'tanto en el atributo como en la propiedad.input.type = 'foo'pone'foo'en el atributo, pero el getter de la propiedad detecta que no es válido y devuelve'text'.
- Aunque
- Algunas propiedades hacen conversión de tipo.
- En ``,
details.getAttribute('open')es una cadena vacía ydetails.openestrue. details.open = falseelimina el atributoopen.details.open = 'hello'no guarda la cadena tal cual en el atributo, sino que la convierte a boolean y deja presente el atributoopen.
- En ``,
- Propiedades como
img.heightconvierten el valor del atributo a número.- El setter convierte a número el valor recibido y trata los negativos como 0.
input.value separa el valor actual del valor predeterminado
- En
inputexisten tanto la propiedadvaluecomo el atributovalue, pero la propiedadvalueno refleja el atributovalue. - La propiedad que sí refleja el atributo
valueesdefaultValue.- El getter de
defaultValuedevuelve el atributovalueo una cadena vacía. - El setter de
defaultValueasigna al atributovalueuna cadena.
- El getter de
- La propiedad
valuesigue inicialmente adefaultValue, pero una vez que se cambia desde JavaScript o por interacción del usuario, pasa a usar un valor interno.- En un `` inicial,
value,defaultValueygetAttribute('value')valen'default'. - Si se ejecuta
input.defaultValue = 'new default', tanto el atributo comovaluese ven como'new default'. - Después, si se ejecuta
input.value = 'hello!',valuepasa a ser'hello!', pero el atributo ydefaultValuesiguen siendo'new default'. - Aunque luego se haga
setAttribute('value', 'another new default'), elvalueactual sigue siendo'hello!'.
- En un `` inicial,
- Al resetear un formulario, el estado interno de
valuepuede volver a inicializarse.
Los atributos son más naturales para configuración, las propiedades para estado
- Es natural separar atributos para configuración y propiedades para estado.
- `` encaja con esta idea, salvo por el nombre.
- El atributo
valuedefine el valor predeterminado. - La propiedad
valueexpone el estado actual.
- El atributo
- También encaja con esta separación el hecho de que al leer o escribir propiedades sí se aplique validación, mientras que con atributos no.
yexpresan el estado abierto con el atributoopen, y el navegador puede agregar o quitar ese atributo directamente según la interacción del usuario.- Ese diseño rompe la idea de que los atributos son solo para configuración.
- Los frameworks que mantienen el DOM o el código JavaScript general deben contemplar que el DOM puede cambiar por sí mismo.
- Un diseño alternativo podría separar un atributo de configuración predeterminada como ``, una propiedad
details.openpara leer y escribir el estado actual, y una pseudoclase de CSS para apuntar al estado. - Simon Peters encontró una discusión de diseño inicial relacionada con esto.
contenteditabletambién rompe esta separación, pero se trata como un opt-in que acepta bastante ruptura.
Cómo lo manejan los frameworks
- Preact y VueJS, salvo algunas excepciones predefinidas, configuran una propiedad cuando
propName in elementes verdadero; si no, configuran un atributo.- Por defecto prefieren propiedades sobre atributos.
- Sus métodos de renderizado a cadena hacen lo contrario e ignoran lo que sea exclusivo de propiedades.
- Implementaciones relacionadas:
setPropertyde Preact,shouldSetAsPropde VueJS
- React, salvo algunas excepciones predefinidas, configura atributos.
- Ese enfoque crea una lógica parecida a la de sus métodos de renderizado a cadena.
- Como las propiedades de elementos personalizados no están en la lista predefinida de React, se asignan como atributos y las APIs exclusivas de propiedades pueden no funcionar.
- En React 19 está previsto cambiar al modelo de Preact/VueJS para elementos personalizados.
- React popularizó el nombre de propiedad
classNamecon una sintaxis que parece atributo, pero internamente configura elatributo class. - Implementación relacionada:
setPropde React
- lit-html mantiene la diferencia entre atributos y propiedades en la sintaxis.
- Si se antepone
.al nombre, como en ``, se configura una propiedad y no un atributo. - Documentación relacionada: Lit expressions
- Si se antepone
3 comentarios
Opiniones en Hacker News
Usar atributos data-* es un buen punto medio entre los enfoques tratados en el artículo.
Estos atributos se exponen automáticamente en JavaScript como propiedades de lectura/escritura de
dataset, por lo que se puede acceder a ellos comodocument.getElementById('myDiv').dataset.payload == "something".Eso sí, hay que tener cuidado con que los atributos HTML usan
kebab-casey JavaScript usacamelCase, así quedata-my-cool-data-attributese convierte endataset.myCoolDataAttribute.En varios ecosistemas que usan
camelCaseno hay una convención establecida sobre cómo tratar las siglas en mayúsculas, así que casos comodataset.myIDquedan ambiguos.kebab-caseycamelCase, uso métodos comoElement.hasAttribute('data-thing'),Element.getAttribute('data-thing')yElement.setAttribute('data-thing', '...').Al inspeccionar o depurar en el DOM, no hace falta pasar por una conversión incómoda, así que la intención queda mucho más clara.
Además, en https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att... describe los atributos globales
data-*como “atributos de datos personalizados que permiten a los scripts intercambiar información propia entre HTML y su representación en el DOM”.Los contextos aquí no se refieren necesariamente a distintos
iframe, sino a casos como las extensiones de navegador, que crean contextos de ejecución de JavaScript separados con acceso al mismo DOM.En la terminología del protocolo de herramientas para desarrolladores, a esto también se le llama isolated worlds.
Lo que se trata en el artículo representa complejidad y comportamientos que no aportan mucho a la vida.
Ese comportamiento solo tiene desventajas y ninguna ventaja; la especificación simplemente debería haber definido los objetos DOM y los objetos JavaScript como la misma cosa, haciendo que atributos y propiedades fueran también el mismo concepto.
Poder almacenar valores que no sean strings en nodos DOM es una ventaja clara, y no es algo que deba despacharse tan a la ligera.
Mientras no se exija que todas las propiedades solo puedan ser strings, no es nada simple.
Hay que considerar conversiones con números, propiedades que pueden establecer objetos y propiedades que no tienen un atributo correspondiente.
Aun así, habría sido mejor tener una convención o regla 100% fija, pero eso no es algo simple.
Métodos como
addEventListenerson propiedades del prototipo de cada elemento DOM, pero vistos como atributos de string no tienen sentido.Entonces los métodos de elementos DOM tendrían que convertirse en funciones libres como
HTMLElement.addEventListener(foo, 'some-event', e => console.log(e));; aunque es cuestión de gustos, en JavaScript es mejor poder usar llamadas a métodos de objeto.Los elementos DOM tienen una interfaz muy específica que cambia el estado con
setAttribute, y no veo por qué habría que esperar que, al pegarle simplemente una propiedad al objeto JavaScript creado para enviar mensajes al DOM mediante esa interfaz, esta se enrute automáticamente.Una API así podría ofrecer una mejor experiencia, pero tiene varios problemas difíciles; aun así, probablemente sería más limpia.
Quizá sea porque llevo demasiado tiempo escribiendo código backend, pero al ver este tipo de interfaz siento que por dentro pasan muchas cosas raras y que algún desarrollador amable del pasado nos dio el wrapper más seguro posible.
Una perspectiva útil para entenderlo es que, en sentido estricto, un atributo es un nodo DOM, es decir, un nodo
Attr.Ese nodo puede tener propiedades como
name,value,ownerElementyprefix, y en conjunto se puede acceder a él como el valor de la propiedadattributesdel elemento propietario, en forma deNamedNodeMapy no de arreglo.Por eso el nombre
getAttribute(name)puede ser engañoso: en realidad no devuelve el nodo de atributo en sí, sino la propiedadvaluede ese nodo de atributo, así que se parece más agetAttributeValue(name).Referencias: 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,getAttributeNamesyhasAttribute, y que solo pueda hacerse con nodos de atributo oel.attributes.Tampoco se pueden mover nodos de atributo entre elementos ni cambiar el orden de los nodos de atributo dentro de un elemento.
Jake, entonces, ¿qué hacemos con esto?: https://jakearchibald.com/2023/against-self-closing-tags-in-...
Entiendo el argumento de no actualizar las propiedades, pero es útil que se pueda apuntar a ellas con CSS
:checkeden los elementos de entradaEs mucho más fácil depurar revisando el DOM de inmediato que pasar por el depurador
Probablemente la diferencia más importante sea la persistencia, pero el artículo no la trata
Los atributos se mantienen mientras se pueda acceder al mismo objeto raíz de ese árbol DOM, por ejemplo mientras no se salga de la página
Las propiedades de objeto solo se mantienen hasta que ese objeto sea recolectado por el recolector de basura
Si el nodo DOM se clona o pasa por serialización/deserialización, se convierte en un nodo DOM completamente distinto con los mismos atributos, por lo que no tendrá las propiedades que no sean valores predeterminados de otro nodo
Es un artículo interesante
Siempre vi los elementos DOM en JavaScript como una especie de proxy
Te permiten trabajar con el elemento DOM real, pero al mismo tiempo, como son simplemente objetos JavaScript, sentía que además de usar funciones que interactúan con el elemento real, también se les pueden asignar valores y hacer lo que uno quiera
Está bien explicado con ejemplos y se lee bien; en particular, incluye varios detalles que a menudo se pasan por alto, así que podría ser útil para mucha gente
Las propiedades DOM también se llaman IDL attributes
Fuera de quienes escriben especificaciones, casi nadie las llama
IDL attributes, y si a ambas cosas se les dice atributos, se vuelve muy confusoEs un buen artículo, y me deja con curiosidad sobre cómo sería una mejor forma de expresarlo
Sería interesante que HTML pudiera tener atributos que no fueran cadenas, y que su valor pudiera referenciar valores locales de una etiqueta
scriptcon scopeMe pareció un poco extraña la idea de ver los atributos como configuración predeterminada
Por ejemplo, si el atributo
classsolo mostrara el valor predeterminado, al alternar clases sería bastante raro que en el inspector DOM o en el HTML serializado no se viera una clase activa, o se viera una clase inactivaEl atributo
classfunciona bien dentro de este esquemaYo, como dueño del documento, configuro el atributo
class, y puedo cambiar esa configuración con el tiempo, pero eso lo hago solo yoEn un esquema alternativo, el navegador agregaría o quitaría clases por su cuenta en respuesta a estados como hover o focus, pero por suerte no lo hace y se usan pseudoclases
https://emewjin.github.io/html-attribute-dom-property/
Tradujeron tanto attribute como property como el mismo “atributo”...