2022 年 8 月 21 日

属性和特性

当浏览器加载页面时,它会“读取”(另一个词:“解析”)HTML 并从中生成 DOM 对象。对于元素节点,大多数标准 HTML 属性会自动成为 DOM 对象的属性。

例如,如果标签是 <body id="page">,则 DOM 对象具有 body.id="page"

但是,属性-特性映射不是一对一的!在本章中,我们将注意区分这两个概念,了解如何使用它们,何时它们相同,何时它们不同。

DOM 属性

我们已经看到了内置的 DOM 属性。有很多。但从技术上讲,没有人限制我们,如果不够,我们可以添加我们自己的属性。

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

例如,让我们在 document.body 中创建一个新属性

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)

我们还可以修改内置原型,例如 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 属性和方法的行为就像常规 JavaScript 对象的属性和方法一样

  • 它们可以具有任何值。
  • 它们区分大小写(写 elem.nodeType,而不是 elem.NoDeTyPe)。

HTML 属性

在 HTML 中,标签可能具有属性。当浏览器解析 HTML 以为标签创建 DOM 对象时,它会识别标准属性并从中创建 DOM 属性。

因此,当元素具有 id 或另一个标准属性时,将创建相应的属性。但如果属性是非标准的,则不会发生这种情况。

例如

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

请注意,一个元素的标准属性对于另一个元素可能是未知的。例如,"type" 对于 <input> (HTMLInputElement) 是标准的,但对于 <body> (HTMLBodyElement) 不是。标准属性在相应元素类的规范中进行了描述。

在这里我们可以看到它

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

因此,如果属性是非标准的,则不会有 DOM 属性。有没有办法访问此类属性?

当然。可以使用以下方法访问所有属性

  • elem.hasAttribute(name) – 检查是否存在。
  • elem.getAttribute(name) – 获取值。
  • elem.setAttribute(name, value) – 设置值。
  • elem.removeAttribute(name) – 删除属性。

这些方法完全按照 HTML 中所写的内容进行操作。

还可以使用 elem.attributes 读取所有属性:属于内置 Attr 类的对象集合,具有 namevalue 属性。

以下是读取非标准属性的演示

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

HTML 属性具有以下特征

  • 它们的名称不区分大小写(idID 相同)。
  • 它们的值始终为字符串。

以下是使用属性的扩展演示

<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 中全部小写。但这无关紧要:属性名称不区分大小写。
  2. 我们可以将任何内容分配给属性,但它会变成字符串。因此,此处我们有 "123" 作为值。
  3. 包括我们设置的所有属性在内的所有属性都在 outerHTML 中可见。
  4. attributes 集合是可迭代的,并且具有元素的所有属性(标准和非标准)作为具有 namevalue 属性的对象。

属性同步

当标准属性发生更改时,相应的属性会自动更新,反之亦然(有一些例外)。

在下面的示例中,id 被修改为属性,并且我们可以看到属性也发生了更改。然后反过来也是如此

<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 仅从属性 → 属性同步,但不会反向同步

<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 会更新属性。
  • 但属性更改不会影响属性。

该“特性”实际上可能派上用场,因为用户操作可能导致 value 更改,然后在这些操作之后,如果我们希望从 HTML 中恢复“原始”值,则它在属性中。

DOM 属性具有类型

DOM 属性并不总是字符串。例如,input.checked 属性(对于复选框)是一个布尔值

<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 属性是一个字符串,但 style 属性是一个对象

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

不过,大多数属性都是字符串。

极少情况下,即使 DOM 属性类型为字符串,它也可能与属性不同。例如,href DOM 属性始终是一个完整 URL,即使该属性包含相对 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 或任何其他属性的值,就像在 HTML 中编写的那样,我们可以使用 getAttribute

非标准属性,数据集

在编写 HTML 时,我们使用了许多标准属性。但是非标准的自定义属性呢?首先,让我们看看它们是否有用?有什么用?

有时,非标准属性用于将自定义数据从 HTML 传递到 JavaScript,或“标记”HTML 元素以供 JavaScript 使用。

像这样

<!-- 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 表示订单状态

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

为什么使用属性比使用 .order-state-new.order-state-pending.order-state-canceled 等类要好?

因为属性更易于管理。状态可以像以下一样轻松更改

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

但是自定义属性可能存在一个潜在问题。如果我们出于自己的目的使用非标准属性,并且稍后标准引入了它并使其执行某些操作,该怎么办?HTML 语言是活跃的,它在不断发展,并且出现了更多属性以满足开发人员的需求。在这种情况下可能会产生意外的影响。

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

所有以“data-”开头的属性都为程序员使用而保留。它们在 dataset 属性中可用。

例如,如果一个 elem 有一个名为 "data-about" 的属性,它可以作为 elem.dataset.about 使用。

像这样

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

data-order-state 这样的多词属性会变成驼峰式: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-* 属性是一种有效且安全的方式来传递自定义数据。

请注意,我们不仅可以读取,还可以修改数据属性。然后,CSS 会相应地更新视图:在上面的示例中,最后一行 (*) 将颜色更改为蓝色。

总结

  • 属性——是写在 HTML 中的内容。
  • 属性——是 DOM 对象中的内容。

一个小小的比较

属性 属性
类型 任何值,标准属性的类型在规范中描述 字符串
名称 名称区分大小写 名称不区分大小写

用于处理属性的方法是

  • elem.hasAttribute(name)——检查是否存在。
  • elem.getAttribute(name)——获取值。
  • elem.setAttribute(name, value)——设置值。
  • elem.removeAttribute(name)——移除属性。
  • elem.attributes 是所有属性的集合。

在大多数情况下,使用 DOM 属性是更可取的。我们应该仅在 DOM 属性不适合我们时才引用属性,例如,当我们需要确切的属性时

  • 我们需要一个非标准属性。但如果它以 data- 开头,那么我们应该使用 dataset
  • 我们希望读取 HTML 中“按原样编写”的值。DOM 属性的值可能不同,例如,href 属性始终是一个完整的 URL,我们可能希望获取“原始”值。

任务

重要性:5

编写代码以从文档中选择具有 data-widget-name 属性的元素并读取其值。

<!DOCTYPE html>
<html>
<body>

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

  <script>
    /* your code */
  </script>
</body>
</html>
<!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>
重要性:3

通过更改其 style 属性使所有外部链接变为橙色。

如果

  • 它的 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="https://node.org.cn">https://node.org.cn</a></li>
  <li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>

<script>
  // setting style for a single link
  let link = document.querySelector('a');
  link.style.color = 'orange';
</script>

结果应该是

为任务打开沙箱。

首先,我们需要找到所有外部引用。

有两种方法。

第一种方法是使用 document.querySelectorAll('a') 找到所有链接,然后过滤出我们需要的链接

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

请注意:我们使用 link.getAttribute('href')。而不是 link.href,因为我们需要 HTML 中的值。

…另一种更简单的方法是将检查添加到 CSS 选择器

// 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');

在沙箱中打开解决方案。

教程地图

评论

在评论前请先阅读此内容…
  • 如果您有改进建议,请 提交 GitHub 问题 或提交拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容,请详细说明。
  • 要插入几行代码,请使用 <code> 标记,要插入多行代码,请将其包装在 <pre> 标记中,要插入 10 行以上的代码,请使用沙盒 (plnkrjsbincodepen…)