MENU

Attributes of HTML and Properties of DOM

2021 年 09 月 14 日 • 阅读: 215 • 前端

当浏览器加载页面时,它 “读取” (或 “解析”)HTML ,以此生成 DOM 对象。对于元素节点,大多数标准 HTML attributes 自动变成 DOM 对象的 properties。

例如,标签 <body id="page"> 对应的 DOM 对象,有 body.id === "page"

但是 attribute-property 并非一一映射!本节我们将留心区分这两个概念,来看看当它们相同和不同时分别如何处理。

DOM Properties

我们已经见过许多内置 DOM property。但如果还不够,我们可以自己添加,这在技术上完全没有限制。

DOM 节点是常规 JavaScript 对象,我们可以改变它们。

例如,让我们在 document.body 上创建一个新 property

document.body.myData = {
  name: 'Caesar',
  title: 'Imperator'
};

alert(document.body.myData.title); // Imperator

我们还可以添加一个方法:

document.body.sayTagName = function() {
  alert(this.tagName);
};

document.body.sayTagName(); // BODY (the value of "this" in the method is document.body)

我们甚至可以修改内置的 prototypes,比如 Element.prototype,来为所有元素添加新方法:

Element.prototype.sayHi = function() {
  alert(`Hello, I'm ${this.tagName}`);
};

document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY

所以,DOM properties 和 methods 表现得就像那些常规 JavaScript 对象:

  • 它们可以有任何值
  • 它们是大小写敏感的(elem.nodeTypeelem.NoDeTyPe 不同)

HTML Attributes

HTML 标签可能包含 attributes。当浏览器通过解析它去创建 DOM 对象时,会识别 standard attributes 并以此创建 DOM properties。

所以当元素有 id 和另一个 standard attribute 时,相应的 property 就会被创建。但如果 attribute 是 non-standard,这种情况不会发生。

比如:

<body id="test" something="non-standard">
  <script>
    alert(document.body.id); // test
    // non-standard attribute does not yield a property
    alert(document.body.something); // undefined
  </script>
</body>

请注意一种元素的 standard attribute 对于另一种可能是未知的。例如,"type"<input> HTMLInputElement 的标准属性,但对于 <body> HTMLBodyElement 并非如此。Standard attributes 声明在相应元素类的规范中。

让我们观察以下代码:

<body id="body" type="...">
  <input id="input" type="text">
  <script>
    alert(input.type); // text
    alert(body.type); // undefined: DOM property not created, because it's non-standard
  </script>
</body>

所以,如果一个 attribute 是 non-standard 的,就不会有对应的 DOM-property。那还能访问这些 attributes 吗?

当然,所有 attributes 都可通过以下方法访问:

  • elem.hasAttribute(name) – 检测是否存在
  • elem.getAttribute(name) – 获取值
  • elem.setAttribute(name, value) – 设置值
  • elem.removeAttribute(name) – 移除 attribute

这些方法操作是和 HTML 内容同步的。

此外可以通过 elem.attributes 读取所有 attributes:一个对象集合,属于内置 Attr 类,有 namevalue properties。

下面是一个读取 non-standard property 的示例:

<body something="non-standard">
  <script>
    alert(document.body.getAttribute('something')); // non-standard
  </script>
</body>

HTML attributes 有以下特点:

  • 它们的名称大小写不敏感(idID 相同)
  • 它们的值总是字符串

下面是一个处理 attributes 的拓展示例:

<body>
  <div id="elem" about="Elephant"></div>

  <script>
    alert( elem.getAttribute('About') ); // (1) 'Elephant', reading

    elem.setAttribute('Test', 123); // (2), writing

    alert( elem.outerHTML ); // (3), see if the attribute is in HTML (yes)

    for (let attr of elem.attributes) { // (4) list all
      alert( `${attr.name} = ${attr.value}` );
    }
  </script>
</body>

请注意:

  1. getAttribute('About') - 首字母是大写,但 HTML 中都是小写。但那不重要:attribute 名大小写不敏感
  2. 我们可以给 attribute 赋任何值,但它会变成一个字符串。所以这儿有值 "123"
  3. 所有 attributes,包括我们设置的那个,都会出现在 outerHTML
  4. attributes 集合是可迭代的,它包含元素的所有 attributes(standard and non-standard)构成的对象,这些对象有 namevalue properties

Property-attribute 同步

当 standard attribute 改变时,相应的 property 会自动更新,反之亦然(有例外情况)。

下面的示例中,id 被作为 attribute 修改,我们可以看到 property 也会跟着改变。反过来也一样:

<input>

<script>
  let input = document.querySelector('input');

  // attribute => property
  input.setAttribute('id', 'id');
  alert(input.id); // id (updated)

  // property => attribute
  input.id = 'newId';
  alert(input.getAttribute('id')); // newId (updated)
</script>

但也有例外,比如 input.value 只会从 attribute 同步到 property,反过来不行:

<input>

<script>
  let input = document.querySelector('input');

  // attribute => property
  input.setAttribute('value', 'text');
  alert(input.value); // text

  // NOT property => attribute
  input.value = 'newValue';
  alert(input.getAttribute('value')); // text (not updated!)
</script>

上例中:

  • 改变 value attribute 会更新 property
  • 但 property 改变不影响 attribute

这个 “特性” 也许真的会派上用场,因为用户动作会导致 value 改变,在那之后,如果我们想从 HTML 恢复 "原始" 值,它就在 attribute 里。

DOM properties 是有类型的

DOM properties 并不总是字符串。例如,input.checked property (checkbox 对应 DOM 对象的 property)是一个 boolean:

<input id="input" type="checkbox" checked> checkbox

<script>
  alert(input.getAttribute('checked')); // the attribute value is: empty string
  alert(input.checked); // the property value is: true
</script>

另一个例子,style attribute 是字符串,但 style property 是一个对象:

<div id="div" style="color:red;font-size:120%">Hello</div>

<script>
  // string
  alert(div.getAttribute('style')); // color:red;font-size:120%

  // object
  alert(div.style); // [object CSSStyleDeclaration]
  alert(div.style.color); // red
</script>

但大多数 properties 是字符串。

还有非常少见的情况,即使 DOM property 的类型是字符串,它也可能与 attribute 不同。比如,DOM property 的 href 总是一个 full URL,即便 attribute 只包含一个相对 URL,甚至是一个 #hash

见下例:

<a id="a" href="#hello">link</a>
<script>
  // attribute
  alert(a.getAttribute('href')); // #hello

  // property
  alert(a.href ); // full URL in the form http://site.com/page#hello
</script>

如果我们需要 href 或其它 attribute 在 HTML 中的原始值,可以使用 getAttribute

Non-standard attributes, dataset

编写 HTML 时,我们会使用很多 standard attributes。但可以使用 non-standard 或自定义 attribute 吗?让我们先看看它们是否有用,能用在哪里。

有时,non-standard attributes 用来将自定义数据从 HTML 传递到 JavaScript,或者为 JavaScript “标记” HTML。

像这样:

<!-- mark the div to show "name" here -->
<div show-info="name"></div>
<!-- and age here -->
<div show-info="age"></div>

<script>
  // the code finds an element with the mark and shows what's requested
  let user = {
    name: "Pete",
    age: 25
  };

  for(let div of document.querySelectorAll('[show-info]')) {
    // insert the corresponding info into the field
    let field = div.getAttribute('show-info');
    div.innerHTML = user[field]; // first Pete into "name", then 25 into "age"
  }
</script>

它们也被用来设置元素样式。

例如,这里使用 order-state attribute 设置订单状态样式:

<style>
  /* styles rely on the custom attribute "order-state" */
  .order[order-state="new"] {
    color: green;
  }

  .order[order-state="pending"] {
    color: blue;
  }

  .order[order-state="canceled"] {
    color: red;
  }
</style>

<div class="order" order-state="new">
  A new order.
</div>

<div class="order" order-state="pending">
  A pending order.
</div>

<div class="order" order-state="canceled">
  A canceled order.
</div>

为何倾向于使用单个 attribute,而非像 .order-state-new, .order-state-pending, .order-state-canceled 这样的类。

因为单个属性更加方便管理,状态可以很容易地改变:

// a bit simpler than removing old/adding a new class
div.setAttribute('order-state', 'canceled');

但自定义 attribute 可能存在一个问题。如果我们使用 non-standard attribute 实现功能,今后标准引入了它们,并赋予了它们一些行为,这会怎么样呢?HTML 语言是活跃的,它持续成长,越来越多的 attributes 出现来满足开发者需求。这可能造成意想不到的影响。

为了避免冲突,存在 data-* 属性。

所有以 “data-” 开头的 attributes 被保留给开发者使用。它们可以通过 dataset property 访问

例如,如果一个 elem 包含名为 data-about 的 attribute,它可以通过 elem.dataset.about 获取。

像这样:

<body data-about="Elephants">
<script>
  alert(document.body.dataset.about); // Elephants
</script>

data-order-state 这样的多单词 attribute 会变成 camel-cased: dataset.orderState

下面是一个重写的 “订单状态” 示例:

<style>
  .order[data-order-state="new"] {
    color: green;
  }

  .order[data-order-state="pending"] {
    color: blue;
  }

  .order[data-order-state="canceled"] {
    color: red;
  }
</style>

<div id="order" class="order" data-order-state="new">
  A new order.
</div>

<script>
  // read
  alert(order.dataset.orderState); // new

  // modify
  order.dataset.orderState = "pending"; // (*)
</script>

使用 data-* attributes 是一个合法且安全的自定义数据传递方式。

请注意我们不仅可以读取,也能修改 data-attributes。这样 CSS 就可以根据值更新视图:上例最后一行 (*) 将颜色改为蓝色。

Summary

  • Attributes – 是写在 HTML 里的内容
  • Properties – 是 DOM 对象中的属性

一个不完整比较:

PropertiesAttributes
类型任何值,standard properties 的类型描述在 spec 中string
名称大小写敏感大小写不敏感

处理 attributes 的方法是:

  • elem.hasAttribute(name) – 检测存在与否
  • elem.getAttribute(name) – 获取值
  • elem.setAttribute(name, value) – 设置值
  • elem.removeAttribute(name) – 移除 attribute
  • elem.attributes 是所有 attributes 的集合

对于大多数情况,更倾向于使用 DOM properties,除非需要精确的 attributes,例如:

  • non-standard attribute。但如果它以 data- 开始,我们应该使用 dataset
  • 读取写在 HTML 中的值。DOM property 的值可能有所不同,例如 href property 总是完整 URL,但我们可能想获得 “原始” 值。

Tasks

Get the attribute

编写代码选择包含 data-widget-name attribute 的元素,并读取它的值。

<!DOCTYPE html>
<html>
<body>

  <div data-widget-name="menu">Choose the genre</div>

  <script>
    // getting it
    let elem = document.querySelector('[data-widget-name]');

    // reading the value
    alert(elem.dataset.widgetName);
    // or
    alert(elem.getAttribute('data-widget-name'));
  </script>
</body>
</html>

Make external links orange

通过 style property 让外链变成橘黄色。

一个链接是外部的,如果:

  • 它的 href 包含 ://
  • 但不以 http://internal.com 开头
<a name="list">the list</a>
<ul>
  <li><a href="http://google.com">http://google.com</a></li>
  <li><a href="/tutorial">/tutorial.html</a></li>
  <li><a href="local/path">local/path</a></li>
  <li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
  <li><a href="http://nodejs.org">http://nodejs.org</a></li>
  <li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>

<script>
let links = document.querySelectorAll('a');

for (let link of links) {
  let href = link.getAttribute('href');
  if (!href) continue; // no attribute

  if (!href.includes('://')) continue; // no protocol

  if (href.startsWith('http://internal.com')) continue; // internal

  link.style.color = 'orange';
}
</script>

另一种方法:

// look for all links that have :// in href
// but href doesn't start with http://internal.com
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);

links.forEach(link => link.style.color = 'orange');

本文译自 Attributes and properties,译者 LOGI

TG 大佬群 QQ 大佬群

返回文章列表 文章二维码
本页链接的二维码
打赏二维码