HTML特性与DOM属性

本文译者为 360 奇舞团前端开发工程师
原文标题:HTML attributes vs DOM properties
原文作者:Jake
原文地址:https://jakearchibald.com/2024/attributes-vs-properties/

特性和属性在本质上是不同的东西。您可以将相同名称的特性和属性设置为不同的值。例如:

<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>

似乎越来越少的开发人员知道这一点,部分原因归功于框架:

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

如果在框架的模板语言中执行以上操作,您将使用类似特性的语法,但在底层,它有时会设置属性,而不是特性,以及何时执行此操作会因框架而异。在某些情况下,它会设置特性和属性作为副作用,但这不是框架的错。

大多数情况下,这些区别并不重要。我认为开发人员可以在不关心特性和属性之间的区别的情况下拥有长期而愉快的职业生涯是件好事。但是,如果您需要深入了解DOM的更低级别,则有所帮助。即使您认为自己了解差异,也许我会涉及您尚未考虑过的一些细节。因此,让我们深入了解一下...

关键区别

在我们深入研究有趣内容之前,让我们先解决一些技术上的差异:

HTML序列化

特性序列化为HTML,而属性不会:

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

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

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

因此,当您在浏览器开发者工具中查看元素面板时,您只会看到元素上的特性,而不会看到属性。

值类型

为了在序列化格式中工作,特性值始终为字符串,而属性可以是任何类型:

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' }

大小写区分

特性名称不区分大小写,而属性名称区分大小写。

<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>

但是,特性值区分大小写。

好吧,事情开始变得模糊的地方是:

反射

看看这个:

<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>

这似乎与文章中的第一个示例相矛盾,但是上述内容之所以有效,仅因为Element具有一个id getter和setter,可以“反射”id特性。

当属性反射特性时,特性是数据的源。当您设置属性时,将更新特性。当您读取属性时,将读取特性。

为了方便起见,大多数规范将为每个定义的特性创建一个等效的属性。在文章开头的示例中无效,因为foo不是规范定义的特性,所以没有指定的foo属性来反射它。

命名差异

这是相对次要的,但有时属性的名称与它反射的特性的名称不同。

在某些情况下,仅仅和属性有大小写区分:

  • 在<img>上,el.crossOrigin反射crossorigin属性。

  • 在所有元素上,el.ariaLabel反射aria-label属性(aria反射器在2023年末变为跨浏览器。在此之前,您只能使用属性)。

在某些情况下,由于旧JavaScript保留字,名称必须更改:

  • 在所有元素上,el.className反射class属性。

  • 在<label>上,el.htmlFor反射for属性。

验证、类型强制和默认值

属性带有验证和默认值,而特性没有:

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'

在这种情况下,验证由type getter处理。 setter允许无效值'foo',但是当getter看到无效值或无值时,它返回'text'。

有些属性执行类型强制:

<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>

在这种情况下,open属性是一个布尔值,返回特性是否存在。 setter还会强制类型 - 即使setter给出'hello',它也会转换为布尔值,而不是直接转到特性。

像img.height之类的属性将属性值强制为数字。setter将传入的值转换为数字,并将负值视为0。

input上的value

value是一个有趣的属性。有一个value属性和一个value特性。但是,value属性不反射value特性。相反,defaultValue属性反射value特性。

实际上,value属性不反射任何特性。这并不罕见,有很多这样的属性(出于某种原因复选框上的offsetWidth,parentNode,indeterminate,以及许多其他属性)。

最初,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;
  }
}

所以:

<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>

如果value特性被命名为defaultvalue,这将更有意义。现在为时已晚。

特性应用于配置

我认为,特性应该用于配置,而属性可以包含状态。我还相信,轻量级DOM树应该有一个单一的所有者。

从这个意义上说,我认为<input value>做得很好(除了命名)。 value特性配置默认值,而value属性提供当前状态。

当获取/设置属性时,应用验证,但获取/设置特性时不应用验证,这也是有道理的。

我说“在我看来”,因为最近的一些HTML元素有所不同。

<details>和<dialog>元素通过open特性表示它们的打开状态,并且浏览器将自动添加/删除此特性以响应用户交互。

我认为这是一个设计错误。它破坏了特性用于配置的想法,但更重要的是,这意味着负责维护DOM的系统(框架或原生JS)需要为DOM自身的更改做好准备。

我认为它应该是:

<details defaultopen>…</details>

并且具有details.open属性以获取/设置当前状态,以及用于针对该状态进行定位的CSS伪类。

更新:Simon Peters找到了一些有关此问题的早期设计讨论。

我想contenteditable也违反了这一合约,但...嗯...这是一种选择,会带来很多破坏。

框架如何处理差异

回到前面的示例:

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

框架如何处理这个?

Preact和VueJS

除了预定义的一组他们偏爱特性的情况外,如果元素中有propName,他们会将prop设置为属性,否则将设置为特性。基本上,他们更喜欢属性而不是特性。他们的render-to-string方法正好相反,并且会忽略仅限于属性的内容。

Preact中的setProperty。VueJS中的shouldSetAsProp。

React

React的做法正好相反。除了预定义的一组他们偏爱属性的情况外,将设置成特性。这使得它们的render-to-string方法在逻辑上类似。

这解释了为什么自定义元素在React中似乎不起作用。由于它们是自定义的,它们的属性不在React的“预定义列表”中,因此它们被设置为特性。任何在自定义元素上仅限于属性的内容都不起作用。这个问题将得到在React 19中得到修复,为了自定义元素,他们将切换到Preact/VueJS模型。

有趣的是,React在一个看起来像特性的地方流行使用className而不是class。但是,即使您使用属性名称而不是特性名称,React也会在幕后设置class特性。

React中的setProp。

lit-html

Lit的处理方式略有不同:

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

它保留了特性和属性之间的区别,如果要设置属性而不是特性,则需要在名称前加上.。

Lit的表达式文档。

结论

这基本上是我知道的有关属性和特性之间区别的所有内容。如果有我漏掉的内容,或者您有任何问题,请在下面的评论中告诉我!

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

b9c2b737e986b5f872fe0cbc0c8651ce.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: HTML 属性是在 HTML 标签中定义的,用来描述 HTML 元素的一些特征或者行为。例如,在一个表单元素的 HTML 代码中,你可能会看到一个 "name" 属性,它的值可以用来标识表单元素。 而 DOM(文档对象模型)属性则是浏览器在解析 HTML 文档时,为每个 HTML 元素建立的一个对象。这个对象有许多属性和方法,可以用来描述这个 HTML 元素的状态和行为。 因此,HTML 属性表示的是 HTML 元素在 HTML 文档中的初始值,而 DOM 属性则是浏览器动态生成的,表示的是当前 HTML 元素的状态。 这两者之间的区别在于,HTML 属性是静态的,它们在 HTML 文档加载完成后就不会再发生变化,而 DOM 属性则是动态的,它们随着用户的操作或者 JavaScript 程序的执行而可能发生变化。例如,如果你在 JavaScript 中使用了 "element.innerHTML" 属性来修改一个 HTML 元素的内容,那么这个元素的 "innerHTML" 属性就会改变。 ### 回答2: HTML 属性DOM 属性是用于操作和改变网页元素的两种不同的方式。 HTML 属性通常在 HTML 标记中定义,并且表示元素的初始状态。它们可以设置在元素的开始标签中,例如`<input type="text" value="Hello">`。HTML 属性的值在页面加载时被设置,然后通常不会再改变。换句话说,它们代表了元素的初始值,不能直接通过 JavaScript 来更新。 DOM 属性是通过 JavaScript 来访问和更新的。DOM 属性可以表示元素当前的状态,包括用户的交互和其他动态事件所导致的变化。DOM 属性是实时更新的,可以随时通过 JavaScript 来读取或修改。这意味着通过 DOM 属性,我们可以获取元素的最新值,无论它是由用户交互、通过 JavaScript 修改还是其他方式改变的。 例如,我们可以使用 HTML 属性 `value` 来设置一个输入框的初始值,但是一旦页面加载完成后,如果用户输入了新的值,HTML 属性的值将不会自动更新。而如果我们使用 DOM 属性 `value`,我们可以随时获取输入框当前的值,而不受初始或其他过时值的限制。 总结起来,HTML 属性是用于设置元素的初始值,而 DOM 属性则是用于获取元素的当前值。HTML 属性通常不会随时间更新,而 DOM 属性是能够实时反映元素状态的。 ### 回答3: HTML 属性DOM 属性都用于表示元素的特性属性。不同之处在于,HTML 属性通常在文档加载时设置,并且通常表示元素的初始值。一旦设置了 HTML 属性,它们不会自动更新以反映元素状态的变化。 相反,DOM 属性是在 DOM 中动态更新的,可以反映元素的最新状态。DOM 属性是通过 JavaScript 来访问和操作的。可以使用 DOM 属性来获取元素的当前值,也可以通过赋值来更新元素的属性。 举个例子来说,假设有一个按钮元素,它有一个 HTML 属性 `disabled` 来表示按钮是否可用。在 HTML 属性中,我们可以设置 `disabled` 的初始值为 `true`,表示按钮初始状态为禁用。一旦页面加载完成,按钮将保持禁用状态,不会自动更新。 然而,使用 JavaScript 可以通过 DOM 属性 `button.disabled` 来访问和修改按钮的禁用状态。通过设置 `button.disabled = false`,我们可以将按钮设置为启用状态。DOM 属性可以反映元素的最新状态,并且可以随时更新。 总结起来,HTML 属性表示元素的初始值,而 DOM 属性表示元素的当前状态。HTML 属性在文档加载时设置,不会自动更新,而 DOM 属性是通过 JavaScript 动态访问和更新的,以反映元素的最新状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值