Attributes of HTML and Properties of DOM
当浏览器加载页面时,它 “读取” (或 “解析”)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.nodeType
和elem.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 类,有 name
和 value
properties。
下面是一个读取 non-standard property 的示例:
<body something="non-standard">
<script>
alert(document.body.getAttribute('something')); // non-standard
</script>
</body>
HTML attributes 有以下特点:
- 它们的名称大小写不敏感(
id
和ID
相同) - 它们的值总是字符串
下面是一个处理 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>
请注意:
getAttribute('About')
- 首字母是大写,但 HTML 中都是小写。但那不重要:attribute 名大小写不敏感- 我们可以给 attribute 赋任何值,但它会变成一个字符串。所以这儿有值
"123"
- 所有 attributes,包括我们设置的那个,都会出现在
outerHTML
attributes
集合是可迭代的,它包含元素的所有 attributes(standard and non-standard)构成的对象,这些对象有name
和value
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 对象中的属性
一个不完整比较:
Properties | Attributes | |
---|---|---|
类型 | 任何值,standard properties 的类型描述在 spec 中 | string |
名称 | 大小写敏感 | 大小写不敏感 |
处理 attributes 的方法是:
elem.hasAttribute(name)
– 检测存在与否elem.getAttribute(name)
– 获取值elem.setAttribute(name, value)
– 设置值elem.removeAttribute(name)
– 移除 attributeelem.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