原文链接:https://javascript.info/dom-attributes-and-properties,translate with ❤️ by zhangbao.

浏览器加载页面时,它“读取”(或称为“解析”)HTML 文本,生成 DOM 对象。几乎所有标准 HTML 标签在转成元素节点时,标签特性在节点对象上都有同名属性与之对应。

例如:对于标签 <body id="page">,对应 DOM 对象 body 上就有一个 id 属性,属性值即是 "page"

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

DOM 属性

我们已经看到一些内置 DOM 属性了。但从技术上讲远可以再多,如果不够,我们可以添加自己的。

DOM 节点也是普通的 JavaScript 对象,是可以修改的。

例如,我们来为 document.body 对象添加新属性:

  1. docuemnt.body.myData = {
  2. name: '凯撒',
  3. title: '皇帝'
  4. };
  5. alert(document.body.title); // 皇帝

也可以添加方法:

  1. document.body.sayName = function () {
  2. alert(this.tagName)
  3. };
  4. document.body.sayName(); // BODY(方法里的 this 就是指 document.body)

我们也可以修改内置原型,比如 Element.prototype,为所有元素节点添加方法:

  1. Element.prototype.sayHi = function () {
  2. alert(`嗨,我是 ${ this.tagName }`);
  3. };
  4. document.documentElement.sayHi(); // 嗨,我是 HTML
  5. document.body.sayHi(); // 嗨,我是 BODY

因此,DOM 属性和方法的行为与常规 JavaScript 对象的行为类似:

  • 可以有任意类型的值。

  • 是大小写敏感的(elem.nodeType 不同于 elem.NoDeTyPe)。

HTML 特性

在 HTML 语言中,标签可以包含特性。当浏览器读取 HTML 文本,并为各个标签创建对应 DOM 对象时,它能识别标准特性并依据它们去创建对应的 DOM 属性。

所以当一个元素包含 id 或另一个标准特性时,就会有对应属性被创建。但如果不是标准特性,就不会在 DOM 对象上创建了(因为浏览器不认识啊)。

例如:

  1. <body id="test" something="non-standard">
  2. <script>
  3. alert(document.body.id); // test
  4. // 对于非标准特性,在生成的 DOM 对象中不会有
  5. alert(document.body.something); // undefined
  6. </script>
  7. </body>

要注意的是,不同类型的标签也有不同的标准特性。例如:"type"<input>HTMLInputElement)的标准特性,但不是 <body>HTMLBodyElement)的。在规范中,描述了各个元素类的标准特性有哪些。

在这里我们可以看到它:

  1. <body id="body" type="...">
  2. <input id="input" type="text">
  3. <script>
  4. alert(input.type); // text
  5. alert(body.type); // undefiend。DOM 中并没有此属性,因为这个属性不是标准特性
  6. </script>
  7. </body>

因此,如果一个特性是非标准的,也就不会有与之对应的 DOM 属性。

但就没有办法访问这个非标准特性了吗?有!

所有的特性都可以通过下列方法获得:

  • elem.hasAttribute(name):检查特性是否存在。

  • elem.getAttribute(name):获得特性值。

  • elem.setAttribute(name, value):设置特性值。

  • elem.removeAttribute(name):删除特性。

这些方法是直接操作 HTML 网页内容的。

当然也可以使用 elem.attributes 读取所有属性,会得到一个属性集合,这个属性集合中的每个成员都属于(内置的) Attr 类型,拥有 namevalue 属性。

下面是读取一个非标准特性的例子:

  1. <body something="non-standard">
  2. <script>
  3. alert(document.body.getAttribute('something')); // non-standard
  4. </script>
  5. </body>

HTML 特性具有下列特点:

  • 名称是大小写不敏感的(idID 一样)。

  • 值总是字符串。

这是使用属性的扩展演示:

  1. <body>
  2. <div id="elem" about="Elephant"></div>
  3. <script>
  4. alert(elem.getAttribute('About')); // (1) 'Elephant'。读取
  5. elem.setAttribute('Test', 123); // (2) 写入
  6. alert(elem.outerHTML); // (3)。查看 elem
  7. for (let attr of elem.attributes) { // (4) 列举所有特性
  8. alert( `${attr.name} = ${attr.value}` );
  9. }
  10. </script>
  11. </body>

注意:

  1. getAttribute('About'):这里的首个字母是大写的,在 HTML 中,所有特性名称都是小写的。但这不会导致问题,因为特性名是大小写不敏感的。

  2. 可以给特性赋值任何类型的值,不过最终都会转换成字符串,所以我们使用 setAttribute('Test', 123) 其实是为特性 test 赋值了字符串 "123"

  3. 所有特性(包括手动设置的)使用 outerHTML 都能看到。

  4. attributes 集合是可迭代的,会迭代出一个个拥有 namevalue 属性的成员。

属性-特性同步

当一个标准特性发生变化时,对应的属性会自动更新,并且反之亦然(有一些例外)。

在下面的例子中,id 特性被修改,相应的属性也会跟着发生变化,反之亦然:

  1. <input>
  2. <script>
  3. let input = document.querySelector('input');
  4. // 特性 => 属性
  5. input.setAttribute('id', 'id');
  6. alert(input.id); // id(更新了)
  7. // 属性 => 特性
  8. input.id = 'newId';
  9. alert(input.getAttribute('id')); // newId(更新了)
  10. </script>

但是也有例外,比如 input.value 就只能从特性=>属性,反过来就不行了。

  1. <input>
  2. <script>
  3. let input = document.querySelector('input');
  4. // 特性 => 属性
  5. input.setAttribute('value', 'text');
  6. alert(input.value); // text(更新了)
  7. // 不行了!(属性 => 特性)
  8. input.value = 'newValue';
  9. alert(input.getAttribute('value')); // text(没更新)
  10. </script>

上面例子里:

  • 设置特性 value 的值更新了对应的属性值。

  • 但是属性值的修改并没有反映到特性上。

这个“功能”实际上可能会派上用场,比如用户可能会修改 value,然后在它之后,如果我们想要从 HTML 恢复“原始”值,它就保留在特性中。

属性值是有类型的

DOM 属性值并不总是字符串。例如:复选框的 input.checked 属性就是布尔类型值:

  1. <input id="input" type="checkbox" checked> 复选框
  2. <script>
  3. alert(input.getAttribute('checked')); // 返回值是一个空字符串 ""
  4. alert(input.checked); // 返回 true
  5. </script>

还有其他的例子。style 特性是一个字符串,但 style 属性是一个对象:

  1. <div id="div" style="color: red; font-size: 120%;">Hello</div>
  2. <script>
  3. // 字符串
  4. alert(div.getAttribute('style')); // "color: red; font-size: 120%;"
  5. // 对象
  6. alert(div.style); // [object CSSStyleDeclaration]
  7. alert(div.style.color); // red
  8. </script>

这是一个重要的区别。 但即使 DOM 属性值类型是字符串,它也可能与对应特性值不同!

例如:DOM 属性 href 的值总是一个完整 URL,即使特性里写的是相对路径,或是一个简单的 #hash(锚点值)。

这有个例子:

  1. <a id="a" href="#hello">链接</a>
  2. <script>
  3. // 特性
  4. alert(a.getAttribute('href')); // #hello
  5. // 属性
  6. alert(a.href); // 完整 URL: http://site.com/page#hello
  7. </script>

如果我们需要 href 值要求完全按照 HTML 中所写的那样,就要用 getAttribute 了。

非标准特性和 dataset

写 HTML 时,我们有许多可用的标准特性。但是对于非标准、自定义的特性呢?首先我们来看它们是否有用武之地,设置它们是为了什么。

非标准特性可以用来将自定义数据从 HTML 传到 JavaScript,或者帮助 JavaScript“标记”HTML。

像这样:

  1. <!-- 标记这个 div 是用来显示“name”的 -->
  2. <div show-info="name"></div>
  3. <!-- 标记这个 div 是用来显示“age”的 -->
  4. <div show-info="age"></div>
  5. <script>
  6. // 下面的代码找到被标记的元素,然后为它们给予需要展示的内容
  7. let user = {
  8. name: 'Pete',
  9. age: 25
  10. };
  11. for (let div of docuemnt.querySelectorAll('[show-info]')) {
  12. // 向对应的元素中插入其需要的数据信息
  13. let field = div.getAttrobute('show-info');
  14. div.innerHTML = user[field]; // Pete, 然后是 age
  15. }
  16. </script>

自定义特性也可以用来为元素定义样式。

例如:现在有一个表示订单状态的特性 order-state

  1. <style>
  2. /* 基于自定义特性赋予元素样式 */
  3. .order[order-state="new"] {
  4. color: green;
  5. }
  6. .order[order-state="pending"] {
  7. color: blue;
  8. }
  9. .order[order-state="canceled"] {
  10. color: red;
  11. }
  12. </style>
  13. <div class="order" order-state="new">
  14. 一张新订单。
  15. </div>
  16. <div class="order" order-state="pending">
  17. 一张进行中的订单。
  18. </div>
  19. <div class="order" order-state="canceled">
  20. 一张已取消订单。
  21. </div>

为什么使用特性比使用诸如 .order-state-new.order-state-pending.order-state-cancled 更可取?

因为使用特性更加方便管理,更易改变:

  1. // 比删除旧的/添加新的 class 更简单
  2. div.setAttribute('order-state', 'canceled');

但是自定义属性可能存在一个问题。如果我们现在使用的非标准属性,之后被标准引入了,而且还有它的含义了,怎么办呢?不要忘了,HTML 语言是活的,它一直在更新,为了满足开发人员的需求,也有越来越多的特性被采纳进来。在这种情况下为了避免冲突,HTML5 引入了 data-* 特性。

所有以“data-”开头的特性表示是给程序员使用的,可通过 dataset 数据集(IE11)属性可以获得它们。

例如:如果一个元素 elem 有一个叫“data-about”的特性,那么就可以用 elem.dataset.about 获得这个特性值。

像这样:

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

获取 data-order-state 这样的多单词特性名,就要使用对应的驼峰形式访问:dataset.orderState

让我们用 data-* 特性重写上面的“订单状态”例子:

  1. <style>
  2. .order[data-order-state="new"] {
  3. color: green;
  4. }
  5. .order[data-order-state="pending"] {
  6. color: blue;
  7. }
  8. .order[data-order-state="canceled"] {
  9. color: red;
  10. }
  11. </style>
  12. <div id="order" class="order" data-order-state="new">
  13. 一张新订单。
  14. </div>
  15. <script>
  16. // 读取
  17. alert(order.dataset.orderState); // new
  18. // 修改
  19. order.dataset.orderState = 'pending'; // (*)
  20. </script>

使用 data-* 的方式定义自定义数据是一个安全有效的方法。

注意的是,我们不仅能读取,也能修改 data-* 特性,相应地元素应用的 CSS 样式也会发生改变。在上面的例子中的 (*) 处,我们把元素的背景色改为蓝色了。

总结

  • 特性(Attributes):写在 HTML 里。

  • 属性(Properties):存在于 DOM 对象中。

小小比较下:

属性 特性
类型 任意类型的值(每个标准属性的值类型都有在规范中描述) 字符串
大小写敏感

与操作特性相关的方法有:

  • elem.hasAttribute(name):检查特性是否存在。

  • elem.getAttribute(name):获得特性值。

  • elem.setAttribute(name, value):设置特性值。

  • elem.removeAttribute(name):删除特性。

  • elem.attributes:获得元素的特性集合。

对于大多数需求,DOM 属性可以很好地为我们服务。只有当 DOM 属性不适合我们时,我们需要确切的特性值,这事才该使用特性。例如:

  • 我们需要一个非标准特性,但是如果是以以 data- 开头的,我们就使用 dataset 访问。

  • 我们需要服务 HTML 中写的值,如果直接使用属性可能会得到不同的值。例如,href 属性返回的总是完整 URL 地址,但我们想要获取“原始”值。

练习题

问题

一、获得特性

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

  1. <!DOCTYPE html>
  2. <html>
  3. <body>
  4. <div data-widget-name="menu">Choose the genre</div>
  5. <script>
  6. /* 你的代码 */
  7. </script>
  8. </body>
  9. </html>

二、让外部链接变橘黄

通过修改外部链接的 style 特性将他们变成橘黄色。

一个外部链接的定义如下:

  1. href 中包含 ://

  2. 不是以 http://internal.com 开头的。

例子:

  1. <a name="list">the list</a>
  2. <ul>
  3. <li><a href="http://google.com">http://google.com</a></li>
  4. <li><a href="/tutorial">/tutorial.html</a></li>
  5. <li><a href="local/path">local/path</a></li>
  6. <li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
  7. <li><a href="http://nodejs.org">http://nodejs.org</a></li>
  8. <li><a href="http://internal.com/test">http://internal.com/test</a></li>
  9. </ul>
  10. <script>
  11. // 设置单个链接的颜色样式
  12. let link = document.querySelector('a');
  13. link.style.color = 'orange';
  14. </script>

答案

一、

  1. // 获得元素
  2. let elem = document.querySelector('[data-widget-name]');
  3. // 读取值
  4. alert(elem.dataset.widgetName);
  5. // 或者
  6. alert(elem.getAttribute('data-widget-name'));

二、

  1. let links = document.querySelectorAll('a'); // 首先找到所有的 a 标签
  2. for (let link of links) {
  3. // 此处使用的是 link.getAttribute 而不是 link.href,因为我们需要的是 HTML 中写的值
  4. let href = link.getAttribute('href');
  5. if (!href) continue; // 排序没有 href 特性的 a 标签
  6. if (!href.includes('://')) continue; // 排除不包含协议的
  7. if (href.startsWith('http://internal.com')) continue; // 排序本站链接
  8. link.style.color = 'orange';
  9. }

(完)