原文链接: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
对象添加新属性:
docuemnt.body.myData = {
name: '凯撒',
title: '皇帝'
};
alert(document.body.title); // 皇帝
也可以添加方法:
document.body.sayName = function () {
alert(this.tagName)
};
document.body.sayName(); // BODY(方法里的 this 就是指 document.body)
我们也可以修改内置原型,比如 Element.prototype
,为所有元素节点添加方法:
Element.prototype.sayHi = function () {
alert(`嗨,我是 ${ this.tagName }`);
};
document.documentElement.sayHi(); // 嗨,我是 HTML
document.body.sayHi(); // 嗨,我是 BODY
因此,DOM 属性和方法的行为与常规 JavaScript 对象的行为类似:
可以有任意类型的值。
是大小写敏感的(
elem.nodeType
不同于elem.NoDeTyPe
)。
HTML 特性
在 HTML 语言中,标签可以包含特性。当浏览器读取 HTML 文本,并为各个标签创建对应 DOM 对象时,它能识别标准特性并依据它们去创建对应的 DOM 属性。
所以当一个元素包含 id
或另一个标准特性时,就会有对应属性被创建。但如果不是标准特性,就不会在 DOM 对象上创建了(因为浏览器不认识啊)。
例如:
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// 对于非标准特性,在生成的 DOM 对象中不会有
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); // undefiend。DOM 中并没有此属性,因为这个属性不是标准特性
</script>
</body>
因此,如果一个特性是非标准的,也就不会有与之对应的 DOM 属性。
但就没有办法访问这个非标准特性了吗?有!
所有的特性都可以通过下列方法获得:
elem.hasAttribute(name)
:检查特性是否存在。elem.getAttribute(name)
:获得特性值。elem.setAttribute(name, value)
:设置特性值。elem.removeAttribute(name)
:删除特性。
这些方法是直接操作 HTML 网页内容的。
当然也可以使用 elem.attributes
读取所有属性,会得到一个属性集合,这个属性集合中的每个成员都属于(内置的) Attr 类型,拥有 name
和 value
属性。
下面是读取一个非标准特性的例子:
<body something="non-standard">
<script>
alert(document.body.getAttribute('something')); // non-standard
</script>
</body>
HTML 特性具有下列特点:
名称是大小写不敏感的(
id
和ID
一样)。值总是字符串。
这是使用属性的扩展演示:
<body>
<div id="elem" about="Elephant"></div>
<script>
alert(elem.getAttribute('About')); // (1) 'Elephant'。读取
elem.setAttribute('Test', 123); // (2) 写入
alert(elem.outerHTML); // (3)。查看 elem
for (let attr of elem.attributes) { // (4) 列举所有特性
alert( `${attr.name} = ${attr.value}` );
}
</script>
</body>
注意:
getAttribute('About')
:这里的首个字母是大写的,在 HTML 中,所有特性名称都是小写的。但这不会导致问题,因为特性名是大小写不敏感的。可以给特性赋值任何类型的值,不过最终都会转换成字符串,所以我们使用
setAttribute('Test', 123)
其实是为特性test
赋值了字符串"123"
。所有特性(包括手动设置的)使用
outerHTML
都能看到。attributes
集合是可迭代的,会迭代出一个个拥有name
和value
属性的成员。
属性-特性同步
当一个标准特性发生变化时,对应的属性会自动更新,并且反之亦然(有一些例外)。
在下面的例子中,id
特性被修改,相应的属性也会跟着发生变化,反之亦然:
<input>
<script>
let input = document.querySelector('input');
// 特性 => 属性
input.setAttribute('id', 'id');
alert(input.id); // id(更新了)
// 属性 => 特性
input.id = 'newId';
alert(input.getAttribute('id')); // newId(更新了)
</script>
但是也有例外,比如 input.value
就只能从特性=>属性,反过来就不行了。
<input>
<script>
let input = document.querySelector('input');
// 特性 => 属性
input.setAttribute('value', 'text');
alert(input.value); // text(更新了)
// 不行了!(属性 => 特性)
input.value = 'newValue';
alert(input.getAttribute('value')); // text(没更新)
</script>
上面例子里:
设置特性
value
的值更新了对应的属性值。但是属性值的修改并没有反映到特性上。
这个“功能”实际上可能会派上用场,比如用户可能会修改 value,然后在它之后,如果我们想要从 HTML 恢复“原始”值,它就保留在特性中。
属性值是有类型的
DOM 属性值并不总是字符串。例如:复选框的 input.checked
属性就是布尔类型值:
<input id="input" type="checkbox" checked> 复选框
<script>
alert(input.getAttribute('checked')); // 返回值是一个空字符串 ""
alert(input.checked); // 返回 true
</script>
还有其他的例子。style
特性是一个字符串,但 style
属性是一个对象:
<div id="div" style="color: red; font-size: 120%;">Hello</div>
<script>
// 字符串
alert(div.getAttribute('style')); // "color: red; font-size: 120%;"
// 对象
alert(div.style); // [object CSSStyleDeclaration]
alert(div.style.color); // red
</script>
这是一个重要的区别。 但即使 DOM 属性值类型是字符串,它也可能与对应特性值不同!
例如:DOM 属性 href
的值总是一个完整 URL,即使特性里写的是相对路径,或是一个简单的 #hash
(锚点值)。
这有个例子:
<a id="a" href="#hello">链接</a>
<script>
// 特性
alert(a.getAttribute('href')); // #hello
// 属性
alert(a.href); // 完整 URL: http://site.com/page#hello
</script>
如果我们需要 href
值要求完全按照 HTML 中所写的那样,就要用 getAttribute
了。
非标准特性和 dataset
写 HTML 时,我们有许多可用的标准特性。但是对于非标准、自定义的特性呢?首先我们来看它们是否有用武之地,设置它们是为了什么。
非标准特性可以用来将自定义数据从 HTML 传到 JavaScript,或者帮助 JavaScript“标记”HTML。
像这样:
<!-- 标记这个 div 是用来显示“name”的 -->
<div show-info="name"></div>
<!-- 标记这个 div 是用来显示“age”的 -->
<div show-info="age"></div>
<script>
// 下面的代码找到被标记的元素,然后为它们给予需要展示的内容
let user = {
name: 'Pete',
age: 25
};
for (let div of docuemnt.querySelectorAll('[show-info]')) {
// 向对应的元素中插入其需要的数据信息
let field = div.getAttrobute('show-info');
div.innerHTML = user[field]; // Pete, 然后是 age
}
</script>
自定义特性也可以用来为元素定义样式。
例如:现在有一个表示订单状态的特性 order-state
:
<style>
/* 基于自定义特性赋予元素样式 */
.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">
一张新订单。
</div>
<div class="order" order-state="pending">
一张进行中的订单。
</div>
<div class="order" order-state="canceled">
一张已取消订单。
</div>
为什么使用特性比使用诸如 .order-state-new
、.order-state-pending
和 .order-state-cancled
更可取?
因为使用特性更加方便管理,更易改变:
// 比删除旧的/添加新的 class 更简单
div.setAttribute('order-state', 'canceled');
但是自定义属性可能存在一个问题。如果我们现在使用的非标准属性,之后被标准引入了,而且还有它的含义了,怎么办呢?不要忘了,HTML 语言是活的,它一直在更新,为了满足开发人员的需求,也有越来越多的特性被采纳进来。在这种情况下为了避免冲突,HTML5 引入了 data-* 特性。
所有以“data-”开头的特性表示是给程序员使用的,可通过 dataset 数据集(IE11)属性可以获得它们。
例如:如果一个元素 elem
有一个叫“data-about
”的特性,那么就可以用 elem.dataset.about
获得这个特性值。
像这样:
<body data-about="Elephants">
<script>
alert(document.body.dataset.about); // Elephants
</script>
获取 data-order-state
这样的多单词特性名,就要使用对应的驼峰形式访问:dataset.orderState
。
让我们用 data-*
特性重写上面的“订单状态”例子:
<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">
一张新订单。
</div>
<script>
// 读取
alert(order.dataset.orderState); // new
// 修改
order.dataset.orderState = 'pending'; // (*)
</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
特性的元素,并读取值。
<!DOCTYPE html>
<html>
<body>
<div data-widget-name="menu">Choose the genre</div>
<script>
/* 你的代码 */
</script>
</body>
</html>
二、让外部链接变橘黄
通过修改外部链接的 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="http://nodejs.org">http://nodejs.org</a></li>
<li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>
<script>
// 设置单个链接的颜色样式
let link = document.querySelector('a');
link.style.color = 'orange';
</script>
答案
一、
// 获得元素
let elem = document.querySelector('[data-widget-name]');
// 读取值
alert(elem.dataset.widgetName);
// 或者
alert(elem.getAttribute('data-widget-name'));
二、
let links = document.querySelectorAll('a'); // 首先找到所有的 a 标签
for (let link of links) {
// 此处使用的是 link.getAttribute 而不是 link.href,因为我们需要的是 HTML 中写的值
let href = link.getAttribute('href');
if (!href) continue; // 排序没有 href 特性的 a 标签
if (!href.includes('://')) continue; // 排除不包含协议的
if (href.startsWith('http://internal.com')) continue; // 排序本站链接
link.style.color = 'orange';
}
(完)