这是用户在 2024-5-16 10:04 为 https://jakearchibald.com/2024/attributes-vs-properties/ 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

HTML attributes vs DOM properties
HTML 属性与 DOM 属性

Attributes and properties are fundamentally different things. You can have an attribute and property of the same name set to different values. For example:
属性和属性是根本不同的东西。可以将相同名称的属性和属性设置为不同的值。例如:

<div foo="bar"></div>
<script>
  const div = document.querySelector('div[foo=bar]');

  console.log(div.getAttribute('foo')); // 'bar'
  console.log(div.foo); // undefined

  div.foo = 'hello world';

  console.log(div.getAttribute('foo')); // 'bar'
  console.log(div.foo); // 'hello world'
</script>

It seems like fewer and fewer developers know this, partially thanks to frameworks:
似乎越来越少的开发人员知道这一点,部分归功于框架:

<input className="" type="" aria-label="" value="" />

If you do the above in a framework's templating language, you're using attribute-like syntax, but under the hood it'll sometimes be setting the property instead, and when it does that differs from framework to framework. In some cases, it'll set a property and an attribute as a side-effect, but that isn't the framework's fault.
如果你在框架的模板语言中执行上述操作,你使用的是类似属性的语法,但在后台,它有时会设置属性,并且何时设置属性因框架而异。在某些情况下,它会将属性和属性设置为副作用,但这不是框架的错。

Most of the time, these distinctions don't matter. I think it's good that developers can have a long and happy career without caring about the differences between properties and attributes. But, if you need to dig down into the DOM at a lower level, it helps to know. Even if you feel you know the difference, maybe I'll touch on a couple of details you hadn't considered. So let's dig in…
大多数时候,这些区别并不重要。我认为开发人员可以拥有漫长而快乐的职业生涯,而不必关心属性和属性之间的差异,这是件好事。但是,如果你需要在较低层次上深入研究 DOM,了解它会有所帮助。即使你觉得你知道其中的区别,也许我会触及一些你没有考虑过的细节。因此,让我们深入研究......

The key differences 主要区别

Before we get to the interesting stuff, let's get some of the technical differences out of the way:
在我们讨论有趣的东西之前,让我们先弄清楚一些技术差异:

HTML serialisation HTML 序列化

Attributes serialise to HTML, whereas properties don't:
属性序列化为 HTML,而属性则不:

const div = document.createElement('div');

div.setAttribute('foo', 'bar');
div.hello = 'world';

console.log(div.outerHTML); // '<div foo="bar"></div>'

So when you're looking at the elements panel in browser developer tools, you're only seeing attributes on elements, not properties.
因此,当您在浏览器开发人员工具中查看元素面板时,您只能看到元素上的属性,而不是属性。

Value types 值类型

In order to work in the serialised format, attribute values are always strings, whereas properties can be any type:
为了在序列化格式中工作,属性值始终是字符串,而属性可以是任何类型:

const div = document.createElement('div');
const obj = { foo: 'bar' };

div.setAttribute('foo', obj);
console.log(typeof div.getAttribute('foo')); // 'string'
console.log(div.getAttribute('foo')); // '[object Object]'

div.hello = obj;
console.log(typeof div.hello); // 'object'
console.log(div.hello); // { foo: 'bar' }

Case sensitivity 区分大小写

Attribute names are case-insensitive, whereas property names are case-sensitive.
属性名称不区分大小写,而属性名称区分大小写。

<div id="test" HeLlO="world"></div>
<script>
  const div = document.querySelector('#test');

  console.log(div.getAttributeNames()); // ['id', 'hello']

  div.setAttribute('FOO', 'bar');
  console.log(div.getAttributeNames()); // ['id', 'hello', 'foo']

  div.TeSt = 'value';
  console.log(div.TeSt); // 'value'
  console.log(div.test); // undefined
</script>

However, attribute values are case-sensitive.
但是,属性值区分大小写。

Ok, here's where things start to get blurry:
好吧,事情开始变得模糊了:

Reflection 反射

Take a look at this:
看看这个:

<div id="foo"></div>
<script>
  const div = document.querySelector('#foo');

  console.log(div.getAttribute('id')); // 'foo'
  console.log(div.id); // 'foo'

  div.id = 'bar';

  console.log(div.getAttribute('id')); // 'bar'
  console.log(div.id); // 'bar'
</script>

This seems to contradict the first example in the post, but the above only works because Element has an id getter & setter that 'reflects' the id attribute.
这似乎与帖子中的第一个示例相矛盾,但上述方法之所以有效,是因为 Element 有一个“反映”属性的 id id getter 和 setter。

When a property reflects an attribute, the attribute is the source of the data. When you set the property, it's updating the attribute. When you read from the property, it's reading the attribute.
当属性反映属性时,该属性是数据的源。设置属性时,它会更新属性。当您从属性读取时,它正在读取属性。

For convenience, most specs will create a property equivalent for every defined attribute. It didn't work in the example at the start of the article, because foo isn't a spec-defined attribute, so there isn't a spec-defined foo property that reflects it.
为方便起见,大多数规范将为每个定义的属性创建一个等效的属性。它在文章开头的示例中不起作用,因为 foo 它不是规范定义的属性,因此没有反映它的规范定义 foo 属性。

Here's the spec for <ol>. The "Content attributes" section defines the attributes, and the "DOM interface" defines the properties. If you click on reversed in the DOM interface, it takes you to this:
下面是 <ol> 的规范。“内容属性”部分定义属性,“DOM 接口”定义属性。如果你在 DOM 界面中单击, reversed 它会带你到这里:

The reversed and type IDL attributes must reflect the respective content attributes of the same name.
reversed type IDL 属性必须反映同名的相应内容属性。

But some reflectors are more complex…
但有些反射器更复杂......

Naming differences 命名差异

Ok, this is relatively minor, but sometimes the property has a different name to the attribute it reflects.
好的,这是相对较小的,但有时属性的名称与它所反映的属性不同。

In some cases it's just to add the kind of casing you'd expect from a property:
在某些情况下,它只是为了添加您期望从属性中获得的那种外壳:

  • On <img>, el.crossOrigin reflects the crossorigin attribute.
    on <img>el.crossOrigin 反映 crossorigin 属性。
  • On all elements, el.ariaLabel reflects the aria-label attribute (the aria reflectors became cross browser in late 2023. Before that you could only use the attributes).
    在所有元素上, el.ariaLabel 都反映了 aria-label 属性(咏叹调反射器在 2023 年底成为跨浏览器。在此之前,您只能使用属性)。

In some cases, names had to be changed due to old JavaScript reserved words:
在某些情况下,由于旧的 JavaScript 保留字,必须更改名称:

  • On all elements, el.className reflects the class attribute.
    在所有元素上, el.className 反映 class 属性。
  • On <label>, el.htmlFor reflects the for attribute.
    on <label>el.htmlFor 反映 for 属性。

Validation, type coercion, and defaults
验证、类型强制和默认值

Properties come with validation and defaults, whereas attributes don't:
属性附带验证和默认值,而属性则不附带:

const input = document.createElement('input');

console.log(input.getAttribute('type')); // null
console.log(input.type); // 'text'

input.type = 'number';

console.log(input.getAttribute('type')); // 'number'
console.log(input.type); // 'number'

input.type = 'foo';

console.log(input.getAttribute('type')); // 'foo'
console.log(input.type); // 'text'

In this case, the validation is handled by the type getter. The setter allowed the invalid value 'foo', but when the getter saw the invalid value, or no value, it returned 'text'.
在这种情况下,验证由 type getter 处理。setter 允许无效值 'foo' ,但当 getter 看到无效值或没有值时,它会返回 'text'

Some properties perform type coercion:
某些属性执行类型强制:

<details open></details>
<script>
  const details = document.querySelector('details');

  console.log(details.getAttribute('open')); // ''
  console.log(details.open); // true

  details.open = false;

  console.log(details.getAttribute('open')); // null
  console.log(details.open); // false

  details.open = 'hello';

  console.log(details.getAttribute('open')); // ''
  console.log(details.open); // true
</script>

In this case, the open property is a boolean, returning whether the attribute exists. The setter also coerces the type - even though the setter is given 'hello', it's turned to a boolean rather than going directly to the attribute.
在本例中,该 open 属性是一个布尔值,返回该属性是否存在。setter 也强制类型 - 即使 setter 是给定 'hello' 的,它也会变成布尔值,而不是直接转到属性。

Properties like img.height coerce the attribute value to a number. The setter converts the incoming value to a number, and treats negative values as 0.
属性等 img.height 将属性值强制为数字。setter 将传入值转换为数字,并将负值视为 0。

value on input fields
value 在输入字段上

value is a fun one. There's a value property and a value attribute. However, the value property does not reflect the value attribute. Instead, the defaultValue property reflects the value attribute.
value 是一个有趣的。有一个 value 属性和一个 value 属性。但是,该 value 属性不反映该 value 属性。相反, defaultValue 该属性反映属性 value

I know, I know.
我知道 我知道。

In fact, the value property doesn't reflect any attribute. That isn't unusual, there's loads of these (offsetWidth, parentNode, indeterminate on checkboxes for some reason, and many more).
事实上,该 value 属性不反映任何属性。这并不罕见,有很多这样的( offsetWidthparentNodeindeterminate 出于某种原因在复选框上,还有很多)。

Initially, the value property defers to the defaultValue property. Then, once the value property is set, either via JavaScript or through user interaction, it switches to an internal value. It's as if it's implemented roughly like this:
最初, value 该属性服从 defaultValue 属性。然后,一旦通过 JavaScript 或用户交互设置了 value 属性,它就会切换到内部值。就好像它的实现大致是这样的:

class HTMLInputElement extends HTMLElement {
  get defaultValue() {
    return this.getAttribute('value') ?? '';
  }

  set defaultValue(newValue) {
    this.setAttribute('value', String(newValue));
  }

  #value = undefined;

  get value() {
    return this.#value ?? this.defaultValue;
  }

  set value(newValue) {
    this.#value = String(newValue);
  }

  // This happens when the associated form resets
  formResetCallback() {
    this.#value = undefined;
  }
}

So:

<input type="text" value="default" />
<script>
  const input = document.querySelector('input');

  console.log(input.getAttribute('value')); // 'default'
  console.log(input.value); // 'default'
  console.log(input.defaultValue); // 'default'

  input.defaultValue = 'new default';

  console.log(input.getAttribute('value')); // 'new default'
  console.log(input.value); // 'new default'
  console.log(input.defaultValue); // 'new default'

  // Here comes the mode switch:
  input.value = 'hello!';

  console.log(input.getAttribute('value')); // 'new default'
  console.log(input.value); // 'hello!'
  console.log(input.defaultValue); // 'new default'

  input.setAttribute('value', 'another new default');

  console.log(input.getAttribute('value')); // 'another new default'
  console.log(input.value); // 'hello!'
  console.log(input.defaultValue); // 'another new default'
</script>

This would have made way more sense if the value attribute was named defaultvalue. Too late now.
如果该 value 属性被命名为 defaultvalue ,这将更有意义。现在为时已晚。

Attributes should be for configuration
属性应用于配置

In my opinion, attributes should be for configuration, whereas properties can contain state. I also believe that the light-DOM tree should have a single owner.
在我看来,属性应该用于配置,而属性可以包含状态。我还认为 light-DOM 树应该有一个单一的所有者。

In that sense, I think <input value> gets it right (aside from the naming). The value attribute configures the default value, whereas the value property gives you the current state.
从这个意义上说,我认为 <input value> 它是正确的(除了命名)。该 value 属性配置默认值,而 value 该属性为您提供当前状态。

It also makes sense that validation applies when getting/setting properties, but never when getting/setting attributes.
在获取/设置属性时应用验证,但在获取/设置属性时从不应用验证也是有道理的。

I say 'in my opinion', because a couple of recent HTML elements have done it differently.
我说“在我看来”,因为最近的几个HTML元素已经以不同的方式做到了这一点。

The <details> and <dialog> elements represent their open state via the open attribute, and the browser will self add/remove this attribute in response to user interaction.
<details><dialog> 元素通过 open 属性表示其打开状态,浏览器将自行添加/删除此属性以响应用户交互。

I think this was a design mistake. It breaks the idea that attributes are for configuration, but more importantly it means that the system in charge of maintaining the DOM (a framework, or vanilla JS) needs to be prepared for the DOM to change itself.
我认为这是一个设计错误。它打破了属性用于配置的想法,但更重要的是,它意味着负责维护 DOM(框架或普通 JS)的系统需要为 DOM 的自我更改做好准备。

I think it should have been:
我认为应该是:

<details defaultopen></details>

And a details.open property to get/set the current state, along with a CSS pseudo-class for targeting that state.
以及 details.open 用于获取/设置当前状态的属性,以及用于定位该状态的 CSS 伪类。

Update: Simon Peters unearthed some of the early design discussion around this.
更新:西蒙·彼得斯(Simon Peters)挖掘了一些围绕此的早期设计讨论。

I guess contenteditable also breaks that contract, but… well… it's a opt-in to a lot of breakage.
我想 contenteditable 也打破了那份合同,但是......井。。。这是对很多破损的选择。

How frameworks handle the difference
框架如何处理差异

Back to the example from earlier:
回到前面的例子:

<input className="" type="" aria-label="" value="" />

How do frameworks handle this?
框架如何处理这个问题?

Preact and VueJS Preact 和 VueJS

Aside from a predefined set of cases where they favour attributes, they'll set the prop as a property if propName in element, otherwise they'll set an attribute. Basically, they prefer properties over attributes. Their render-to-string methods do the opposite, and ignore things that are property-only.
除了一组预定义的情况之外,他们更喜欢属性,如果 propName in element ,他们会将道具设置为属性,否则他们将设置属性。基本上,他们更喜欢属性而不是属性。他们的 render-to-string 方法则相反,并忽略仅属性的内容。

React 反应

React does things the other way around. Aside from a predefined set of cases where they favour properties, they'll set an attribute. This makes their render-to-string method similar in logic.
React 以相反的方式做事。除了一组预定义的情况之外,他们更喜欢属性,他们还将设置一个属性。这使得他们的 render-to-string 方法在逻辑上相似。

This explains why custom elements don't seem to work in React. Since they're custom, their properties aren't in React's 'predefined list', so they're set as attributes instead. Anything that's property-only on the custom element simply won't work. This will be fixed in React 19, where they'll switch to the Preact/VueJS model for custom elements.
这就解释了为什么自定义元素在 React 中似乎不起作用。由于它们是自定义的,因此它们的属性不在 React 的“预定义列表”中,因此它们被设置为属性。自定义元素上仅包含属性的任何内容都不起作用。这将在 React 19 中修复,他们将切换到 Preact/VueJS 模型来处理自定义元素。

The funny thing is, React popularised using className instead of class in what looks like an attribute. But, even though you're using the property name rather than the attribute name, React will set the class attribute under the hood.
有趣的是,React 普及了使用 className 而不是 class 看起来像属性的东西。但是,即使你使用的是属性名称而不是属性名称,React 也会在后台设置 class 属性。

lit-html

Lit does things a little differently:
Lit 的做法略有不同:

<input type="" .value="" />

It keeps the distinction between attributes and properties, requiring you to prefix the name with . if you want to set the property rather than the attribute.
它保留了属性和属性之间的区别,如果要设置属性而不是属性,则要求您在名称前面加上前缀 .

And that's yer lot
这就是你们的命运

That's pretty much everything I know about the difference between properties and attributes. If there's something I've missed, or you have a question, let me know in the comments below!
这几乎是我所知道的关于属性和属性之间区别的所有信息。如果我错过了什么,或者您有问题,请在下面的评论中告诉我!

Thanks to my podcast husband Surma for his usual reviewing skills.
感谢我的播客丈夫苏尔玛(Surma)一贯的审查技巧。

View this page on GitHub
在 GitHub 上查看此页

Jake Archibald in a garden with a black cat

Hello, I'm Jake and that's me there. The one that isn't a cat. I'm a developer of sorts.
你好,我是杰克,这就是我。那不是猫的。我是某种意义上的开发人员。

Elsewhere 别处

Contact 联系

Feel free to throw me an email, unless you're a recruiter, or someone trying to offer me 'sponsored content' for this site, in which case write your request on a piece of paper, and fling it out the window.
请随时给我发一封电子邮件,除非你是招聘人员,或者有人试图为我提供这个网站的“赞助内容”,在这种情况下,把你的要求写在一张纸上,然后把它扔出窗外。