A diferença entre atributos HTML e propriedades DOM
(jakearchibald.com)- Mesmo quando estão no mesmo elemento, atributos HTML e propriedades DOM podem funcionar como espaços de armazenamento separados, então uma mudança em um lado pode não ser refletida automaticamente no outro
- Os atributos aparecem na serialização HTML e no painel de elementos das ferramentas de desenvolvedor, mas as propriedades são estado exclusivo do DOM, então não aparecem em resultados como
outerHTML - Os valores de atributos são sempre strings e seus nomes não diferenciam maiúsculas de minúsculas, enquanto propriedades podem ter vários tipos, como objetos, e seus nomes diferenciam maiúsculas de minúsculas
- Existem propriedades que refletem atributos, como
id,className,htmlForeariaLabel, mas também há exceções comoinput.value, em que estado atual e configuração padrão ficam separados - Cada framework define isso de forma diferente: Preact e VueJS geralmente priorizam propriedades, React geralmente prioriza atributos, e Lit distingue os dois explicitamente com a sintaxe
.value
Atributos e propriedades podem ser diferentes mesmo com o mesmo nome
- Atributos (attributes) e propriedades (properties) podem funcionar como espaços de armazenamento diferentes
- Em ```,
getAttribute('foo')retorna'bar', masdiv.foopode serundefined - Depois, mesmo que
div.foo = 'hello world'seja definido, o valor do atributofoocontinua sendo'bar'
- Em ```,
- Em templates de frameworks, mesmo que se use uma sintaxe que parece atributo, como ````, internamente o framework pode definir uma propriedade dependendo do caso
- Quando uma propriedade é usada depende de cada framework
- Algumas definições podem gerar efeitos colaterais que alteram tanto a propriedade quanto o atributo
O que permanece na serialização HTML são os atributos
- Os atributos existem tanto no HTML quanto no DOM, mas as propriedades existem apenas no DOM
- Mesmo executando
div.setAttribute('foo', 'bar')ediv.hello = 'world', emdiv.outerHTMLsó aparecem os atributos, como em `
`
- O painel de elementos das ferramentas de desenvolvedor do navegador mostra os atributos do elemento, não o conjunto completo de propriedades
Tipo dos valores e tratamento de maiúsculas e minúsculas
- Valores de atributos são sempre strings, porque precisam funcionar em formato serializado
- Se um objeto for passado para
setAttribute, ele será armazenado como a string"[object Object]" - Se o mesmo objeto for atribuído a uma propriedade, ele pode continuar com o tipo objeto
- Se um objeto for passado para
- Nomes de atributos não diferenciam maiúsculas de minúsculas
HeLlO="world"pode ser normalizado no DOM como o atributohellosetAttribute('FOO', 'bar')também pode aparecer como o atributofoo
- Nomes de propriedades diferenciam maiúsculas de minúsculas
- Mesmo que
div.TeSt = 'value'seja definido,div.testpode continuarundefined
- Mesmo que
- Diferentemente do nome dos atributos, os valores dos atributos diferenciam maiúsculas de minúsculas
Propriedades que refletem atributos
- Algumas propriedades refletem (reflect) um atributo específico
div.idfunciona como getter/setter que lê e escreve o atributoid- Ao executar
div.id = 'bar', o atributoidtambém muda para'bar'
- Em uma relação de reflexão, o atributo é a fonte dos dados
- Definir a propriedade atualiza o atributo
- Ler a propriedade lê o valor do atributo
- Atributos definidos pela especificação frequentemente ganham uma propriedade correspondente por conveniência
- Um atributo arbitrário
foonão é um atributo definido pela especificação, então uma propriedade correspondentefoonão é criada automaticamente
- Um atributo arbitrário
- A especificação de `` define atributos em “Content attributes” e propriedades em “DOM interface”
- As propriedades IDL
reversedetypedevem reflect os atributos de conteúdo com o mesmo nome
- As propriedades IDL
Relações de reflexão em que o nome muda
- Algumas propriedades têm nome diferente do atributo que refletem
- Em alguns casos, o nome muda para combinar com o estilo de propriedades do JavaScript
- Em ```,
el.crossOriginreflete o atributocrossorigin - Em qualquer elemento,
el.ariaLabelreflete o atributoaria-label - As propriedades reflexivas de ARIA passaram a funcionar entre navegadores no fim de 2023; antes disso, só era possível usar atributos
- Em ```,
- Em outros casos, a diferença de nome vem de palavras reservadas antigas do JavaScript
- Em qualquer elemento,
el.classNamereflete o atributoclass - Em ```,
el.htmlForreflete o atributofor
- Em qualquer elemento,
Validação, conversão de tipo e valores padrão
- Propriedades podem ter validação e valores padrão, mas atributos por si só não têm esse comportamento
input.typemostra bem a diferença entre atributo e propriedade- Mesmo que
input.getAttribute('type')sejanull,input.typeretorna o valor padrão'text' input.type = 'number'faz com que atributo e propriedade pareçam'number'input.type = 'foo'grava'foo'no atributo, mas o getter da propriedade detecta o valor inválido e retorna'text'
- Mesmo que
- Algumas propriedades fazem conversão de tipo
- Em ```,
details.getAttribute('open')é uma string vazia edetails.openétrue details.open = falseremove o atributoopendetails.open = 'hello'não grava a string literal no atributo; em vez disso, converte para boolean e faz o atributoopenexistir
- Em ```,
- Propriedades como
img.heightconvertem o valor do atributo para número- O setter converte o valor recebido em número e trata valores negativos como 0
input.value separa valor atual e valor padrão
inputtem tanto a propriedadevaluequanto o atributovalue, mas a propriedadevaluenão reflete o atributovalue- A propriedade que reflete o atributo
valueédefaultValue- O getter de
defaultValueretorna o atributovalueou uma string vazia - O setter de
defaultValuedefine o atributovaluecomo string
- O getter de
- A propriedade
valueinicialmente seguedefaultValue, mas, depois que é definida por JavaScript ou por interação do usuário, passa a usar um valor interno- No estado inicial de ````,
value,defaultValueegetAttribute('value')são todos'default' - Ao executar
input.defaultValue = 'new default', tanto o atributo quantovaluepassam a parecer'new default' - Depois, ao executar
input.value = 'hello!',valuepassa a ser'hello!', mas o atributo edefaultValuecontinuam'new default' - Mesmo executando
setAttribute('value', 'another new default'), ovalueatual continua'hello!'
- No estado inicial de ````,
- Quando o formulário é resetado, o estado interno de
valuepode ser reinicializado
Atributos são mais naturais para configuração, propriedades para estado
- É natural separar atributos para configuração (configuration) e propriedades para estado (state)
- ```
input.valueé um exemplo que se encaixa nessa divisão, tirando o nome- O atributo
valuedefine o valor padrão - A propriedade
valuefornece o estado atual
- O atributo
- A distinção também combina com o fato de que há validação ao ler ou escrever propriedades, mas não ao ler ou escrever atributos
eexpressam o estado aberto com o atributoopen, e o navegador adiciona ou remove esse atributo diretamente conforme a interação do usuário- Esse design quebra a ideia de que atributos servem para configuração
- Frameworks que mantêm o DOM, ou código JavaScript comum, precisam lidar com situações em que o DOM muda por conta própria
- Um design alternativo poderia separar um atributo de configuração padrão como ```, uma propriedade
details.openpara ler e escrever o estado atual e uma pseudoclasse CSS para mirar esse estado - Simon Peters encontrou uma discussão inicial de design relacionada a isso
contenteditabletambém quebra essa divisão, mas é tratado como um opt-in que aceita bastante comportamento problemático
Como cada framework lida com isso
- Preact e VueJS, exceto por algumas exceções predefinidas, definem uma propriedade quando
propName in elementé verdadeiro; caso contrário, definem um atributo- Por padrão, priorizam propriedades em vez de atributos
- Métodos de renderização para string fazem o oposto e ignoram itens exclusivos de propriedades
- Implementações relacionadas:
setPropertydo Preact,shouldSetAsPropdo VueJS
- React, exceto por algumas exceções predefinidas, define um atributo
- Essa abordagem cria uma lógica parecida com a dos métodos de renderização em string
- Como propriedades de elementos customizados não estão na lista predefinida do React, elas são definidas como atributos, e APIs exclusivas de propriedades podem não funcionar
- No React 19, elementos customizados devem passar para o modelo usado por Preact/VueJS
- React popularizou o nome de propriedade
classNamecom uma sintaxe que parece atributo, mas internamente define oatributo class - Implementação relacionada:
setPropdo React
- lit-html mantém a diferença entre atributos e propriedades na própria sintaxe
- Em algo como ```, colocar
.antes do nome faz com que seja definida uma propriedade, e não um atributo - Documentação relacionada: Lit expressions
- Em algo como ```, colocar
3 comentários
Opiniões do Hacker News
Usar atributos data-* acaba sendo um bom meio-termo entre as abordagens tratadas no texto
Esses atributos são expostos automaticamente em JavaScript como propriedades de leitura/escrita de
dataset, podendo ser acessados como emdocument.getElementById('myDiv').dataset.payload == "something"Mas é preciso tomar cuidado porque atributos HTML usam
kebab-casee JavaScript usacamelCase, entãodata-my-cool-data-attributeviradataset.myCoolDataAttributeEm vários ecossistemas que usam
camelCase, não há uma convenção estabelecida sobre como lidar com siglas em maiúsculas, o que torna casos comodataset.myIDambíguoskebab-caseecamelCase, uso métodos comoElement.hasAttribute('data-thing'),Element.getAttribute('data-thing')eElement.setAttribute('data-thing', '...')Ao inspecionar ou depurar no DOM, não é preciso passar por uma conversão estranha, então a intenção fica muito mais clara
Além disso, em https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att..., os atributos globais
data-*são descritos como “atributos de dados personalizados que permitem que scripts troquem informações próprias entre o HTML e a representação no DOM”Os contextos aqui não significam necessariamente
iframes diferentes, mas casos como extensões de navegador, que criam contextos separados de execução JavaScript acessando o mesmo DOMNa terminologia do protocolo de ferramentas de desenvolvedor, isso às vezes é chamado de isolated worlds
O que o texto aborda representa complexidades e comportamentos que não acrescentam muito à vida
Esse tipo de comportamento só traz desvantagens, sem benefícios, e a especificação deveria simplesmente ter definido objetos DOM e objetos JavaScript como a mesma coisa, tornando atributos e propriedades o mesmo conceito
Poder armazenar valores que não são strings em nós DOM é uma vantagem clara, e não é algo a se descartar tão levianamente
A menos que se exija que todas as propriedades possam ser apenas strings, isso não é nada simples
Seria preciso considerar conversões com números, propriedades em que é possível definir objetos e até propriedades que não correspondem a atributos
Ainda assim, teria sido melhor se houvesse uma convenção ou regra 100% fixa, mas isso não é uma tarefa simples
Métodos como
addEventListenersão propriedades do protótipo de elementos DOM individuais, mas não fazem sentido como atributos de stringNesse caso, métodos de elementos DOM teriam de virar funções livres, como
HTMLElement.addEventListener(foo, 'some-event', e => console.log(e));; embora seja uma questão de gosto, é melhor poder usar chamadas de método em objetos em JavaScriptElementos DOM têm uma interface muito específica para mudar estado com
setAttribute, e não vejo por que deveríamos esperar que, ao anexar propriedades diretamente ao objeto JavaScript criado para enviar mensagens ao DOM por essa interface, isso fosse automaticamente roteadoUma API assim poderia proporcionar uma experiência melhor, mas há alguns problemas difíceis; ainda assim, acho que ficaria mais limpa
Talvez por eu ter escrito código de backend por tempo demais, quando vejo interfaces desse tipo sinto que há muita coisa estranha por dentro, como se algum desenvolvedor gentil do passado tivesse nos dado o wrapper mais seguro possível
Uma perspectiva que ajuda a entender é que atributos, no sentido estrito, são nós DOM, isto é, nós
AttrEsse nó pode ter propriedades como
name,value,ownerElementeprefix, e, como um todo, pode ser acessado não como um array, mas como umNamedNodeMap, pelo valor da propriedadeattributesdo elemento proprietárioPor isso, o nome
getAttribute(name)é enganoso; na prática, ele não retorna o próprio nó de atributo, mas a propriedadevaluedesse nó, então seria mais próximo degetAttributeValue(name)Referências: 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
el.attributes, e não comsetAttribute,getAttribute,getAttributeNamesouhasAttributeTambém não dá para mover nós de atributo entre elementos, nem alterar a ordem dos nós de atributo dentro de um elemento
Jake, então o que fazemos com isto: https://jakearchibald.com/2023/against-self-closing-tags-in-...
Entendo o argumento de não atualizar a propriedade, mas é útil poder direcionar o alvo via CSS
:checkedem elementos de entradaÉ muito mais fácil depurar verificando o DOM imediatamente do que passando pelo depurador
Talvez a diferença mais importante seja a persistência, mas o texto não aborda isso
Atributos são mantidos enquanto for possível acessar o mesmo objeto raiz daquela árvore DOM, por exemplo sem sair da página
Propriedades de objeto só são mantidas até o objeto ser coletado pelo garbage collector
Se o nó DOM for clonado ou passar por serialização/desserialização, ele se torna um nó DOM completamente diferente com os mesmos atributos, portanto não terá propriedades de outro nó que não sejam valores padrão
É um texto interessante
Sempre vi elementos DOM em JavaScript como uma espécie de proxy
Eles permitem trabalhar com os elementos DOM reais, mas, ao mesmo tempo, são simplesmente objetos JavaScript, então eu sentia que, além de usar funções que interagem com o elemento real, também dava para atribuir valores e fazer o que quisesse
É bom ver uma explicação clara com exemplos, especialmente porque há vários detalhes que muitas vezes passam batidos; deve ajudar muita gente
Propriedades do DOM também são chamadas de IDL attributes
Fora autores de especificações, pouca gente chama isso de
IDL attributes, e chamar ambos de atributos fica muito confusoBom texto; fiquei curioso sobre como seria uma forma melhor de expressar isso
Seria interessante se HTML pudesse ter atributos que não fossem strings, e se seus valores pudessem referenciar valores locais de uma tag
scriptcom escopoAchei um pouco estranha a visão de atributos como configuração padrão
Por exemplo, se o atributo
classmostrasse apenas o valor padrão, ao alternar classes ficaria bem estranho: a classe ativa não apareceria no inspetor do DOM ou no HTML convertido em string, ou uma classe inativa apareceriaO atributo
classfunciona bem dentro desse sistemaEu, como dono do documento, defino o atributo
classe posso alterar essa configuração ao longo do tempo, mas só eu faço issoEm um sistema alternativo, o navegador adicionaria e removeria classes por conta própria em resposta a estados como hover ou focus; felizmente ele não faz isso, e são usadas pseudoclasses
https://emewjin.github.io/html-attribute-dom-property/
Acabaram traduzindo tanto attribute quanto property como “atributo”...