🍥 JavaScript DOM 操作
# 操作 DOM
每一个 DOM 元素节点都属于一个特定的类。这些类形成层次结构。
例如,标签
<a>
相对应的元素节点具有链接相关的(link-related)属性,标签<input>
相对应的元素节点具有与输入相关的属性;文本节点与元素节点不同。但是所有这些标签节点之间也存在共有的属性和方法,因为所有类型的 DOM 节点都形成了一个单一层次的结构。
最终 层次结构 的根节点是
EventTarget
类,Node
节点类继承自它,其他 DOM 节点继承自Node
类。
点击查看
EventTarget
:根「抽象」类。它作为一个基础,让所有的节点都支持 事件行为。(没有实现对象)Node
:抽象类。是 DOM 节点的基础,提供了 DOM 树的核心功能(parentNode
、nextSibling
、childNodes
等getter
)(没有实现对象)但是有继承自它的具体节点类,Text
文本节点、Element
元素节点、注释节点等。Element
:DOM 元素节点的基础类。提供了 元素级导航的getter
。特殊的元素基础节点类继承自它,SVGElement
、XMLElement
、HTMLElement
等。HTMLElement
:是所有的 HTML 元素基础类。各类的 HTML 元素继承自它:HTMLInputElement
:<input>
元素的类;HTMLBodyElement
、HTMLAnchorElement
… 等。
一些元素没有特定的属性,例如
<span>
、<section>
、<article>
,都是HTMLElement
的实例。
从一个 DOM 元素角度出发,例如
<input>
的全部属性和方法的来源(都是来自继承下列的类):
HTMLInputElement
:该类提供特定于输入的属性;HTMLElement
:提供了通用的 HTML 元素方法(以及 getter 和 setter);Element
:提供通用泛型元素方法;Node
:提供通用 DOM 节点属性;EventTarget
: 为事件(包括事件本身)提供支持;- 最后包括
Object
普通对象的方法。
对于 对象 是适用的属性和方法,DOM 节点也适用:
获取名称:
以获取引用类的名称为例,通过
constructor
获取:console.log(document.body.constructor.name) // HTMLBodyElement
1toString
方法:console.log(document.body) // [object HTMLBodyElement]
1检查继承
instanceof
:console.log(document.body instanceof HTMLBodyElement) // true console.log(document.body instanceof HTMLElement) // true console.log(document.body instanceof Element) // true console.log(document.body instanceof Node) // true console.log(document.body instanceof EventTarget) // true
1
2
3
4
5
console...
开发者命令行输出 DOM 元素:
console.log
:显示元素的 DOM 树。console.dir
:显示为 DOM 对象,展示属性和方法。
# 读取 DOM 元素属性
- 获取 DOM 节点的节点 / 标签名称:
tagName
:仅适用于Element
元素节点。nodeName
:适用于任意的 Node 节点。
标签名称的大小写问题:
- 在浏览器 HTML 模式文档中,
tagName/nodeName
始终是大写的:它是BODY
,而不是<body>
或<BoDy>
。- 在 XML 模式中,大小写保持为原样。
- 获取元素名称:
ele.name
- 获取元素
id
:ele.id
- 获取元素值
value
:ele.value
- 获取元素的类名:
ele.className
# 写入 HTML
# innerHTML
innerHTML
允许将元素中的 HTML 获取为字符串形式,并且可以修改它。是更改页面最有效的方法之一。
🌰 例子 / 获取 body
的内容,完全替换:
<body>
<p>A paragraph</p>
<div>A div</div>
<script>
consoel.log(document.body.innerHTML); // 读取当前内容
document.body.innerHTML = 'The new BODY!'; // 替换它
</script>
</body>
2
3
4
5
6
7
8
9
10
🌰 例子 / 插入无效的 HTML 会被修复(未闭合的标签):
<body>
<script>
document.body.innerHTML = '<b>test'; // 忘记闭合标签
console.log( document.body.innerHTML ); // <b>test</b>(被修复了)
</script>
</body>
2
3
4
5
6
7
8
对于脚本标签
<script>
无效,可以插入但是不会执行。
注意
使用 innerHTML+=
会完全重写掉原来的内容并且附加的内容。
例如:
chatDiv.innerHTML += "<div>Hello<img src='smile.gif'/> !</div>";
chatDiv.innerHTML += "How goes?";
2
技术上,
innerHTML
完成了两项工作:移除原来的内容;写入新的内容。
因为内容被重写,所以所有的图片和资源都会重新加载;这对于一些输入、选中等类型的元素是致命的,这会丢失原来的数据。
# outerHTML
outerHTML
属性包含了元素的 完整 HTML。 innerHTML
的内容加上元素标签。
🌰 例子:
<div id="elem">Hello <b>World</b></div>
<script>
console.log(elem.outerHTML); // <div id="elem">Hello <b>World</b></div>
</script>
2
3
4
5
注意:与 innerHTML
不同,写入 outerHTML
不会改变元素。而是在 DOM 中替换它。
🌰 例子 / 选中一个 DOM 元素,使用 outerHTML
替换:
<div>
hello world!
</div>
2
3
let div = querySelector("div")
div.outerHTML = '<p>new world</p>'
console.log(div.outerHTML) // <div> hello world! </div>
2
3
尽管第二行中,使用了
outerHTML
替换掉原来的内容,在外部 HTML 文档可以看到原来的内容被替换。但是原来的outerHTML
内容不会改变。
说明, outerHTML
赋值不会修改 DOM 元素, 而是将其从 DOM 中删除并在其位置插入新的 HTML。
所以,上面的例子 outerHTML
完成了:将 div
从文档中移除;另一个 HTML 文档片段被插入到其位置; div
仍然拥有旧的值,新的 HTML 没有被赋值给任何变量。
仍然可以通过 选择器 获取新的元素的引用;并且原来的获取的元素仍然保留。
注意
innerHTML
与 outerHTML
只对 元素节点 有效。
# nodeValue
/ data
文本节点
对于其他节点类型,例如文本节点类型,具有它们的对应项: nodeValue
和 data
属性。两种属性只有细微的差异。
🌰 例子 / 使用 data
获取节点的内容:
<body>
Hello
<!-- Comment -->
<script>
let text = document.body.firstChild;
console.log(text.data); // Hello
let comment = text.nextSibling;
console.log(comment.data); // Comment
</script>
</body>
2
3
4
5
6
7
8
9
10
11
# textContent
纯文本
textContent
提供了对元素内的 文本 的访问权限:仅文本,去掉所有 标签。
🌰 例子 / 读取纯文本内容:
<div id="news">
<h1>Headline!</h1>
<p>Martians attack people!</p>
</div>
2
3
4
console.log(news.textContent); // Headline! Martians attack people!
- 使用
innerHTML
,将其 作为 HTML 插入,带有所有 HTML 标签会被作为 HTML 处理。- 使用
textContent
,将其 作为文本 插入,所有符号均按字面意义处理。
🌰 例子 / 安全方式写入文本:
<div id="elem1"></div>
<div id="elem2"></div>
2
<script>
let name = prompt("What's your name?", "<b>Winnie-the-Pooh!</b>");
elem1.innerHTML = name;
elem2.textContent = name;
</script>
2
3
4
5
6
# hidden
属性
用于 指定 DOM 元素是否可见。
🌰 例子:
<div>Both divs below are hidden</div>
<div hidden>With the attribute "hidden"</div>
<div id="elem">JavaScript assigned the property "hidden"</div>
<script>
let elem = document.querySelector('#elem')
elem.hidden = true;
</script>
2
3
4
5
6
7
8
9
10
实现的效果与
style="display: none"
相同。
# 区分 Atrributes
与 properties
在浏览器页面加载时,会解析 HTML 从中生成 DOM 对象。对于元素节点 的大多数 HTML Attributes 特性 会自动变成 DOM 对象节点的 properties 属性。但是特性和属性映射不是一一对应的。
🌰 例如: <body id="page">
中 id
特性会对应属性 body.id=“page”
。
# DOM 属性 Property
由于 DOM 元素对象也是 JavaScript 常规对象,所以可以更改它们。
- 可以有很多值。
- 键大小写敏感。
🌰 例子:
document.body.myData = {
name: "name",
title: "title"
}
console.log(document.body.myData.name) // "name"
2
3
4
5
6
添加一个方法:
document.body.sayTagName = function () {
console.log(this.tagName)
}
document.body.sayTagName() // BODY
2
3
4
5
修改原型:
Element.prototype.sayHi = funciton () {
console.log(`HI, IAM ${this.tagName}`)
}
document.documentElement.sayHi() // HI, IAM HTML
document.body.sayHi() // HI, IAM BODY
2
3
4
5
6
# HTML 特性 Attribute
指的是 HTML 标签中的 标准特性 并且以此创建 DOM 属性。
HTML 特性有以下几个特征:
- 它们的名字是大小写不敏感的(
id
与ID
相同)。 - 它们的值总是字符串类型的。
🌰 例子:
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// 非标准的特性没有获得对应的属性
alert(document.body.something); // undefined
</script>
</body>
2
3
4
5
6
7
可以看出:对应的 标准特性
id
才会创建 DOM 属性。非标准的特性不会。但是不同的标签,标准的特性可能会不同。例如
type
特性是<input>
的一个标准的特性(HTMLInputElement (opens new window)),但对于<body>
(HTMLBodyElement (opens new window))来说则不是。规范中对相应元素类的标准的属性进行了详细的描述。<body id="body" type="..."> <input id="input" type="text"> <script> alert(input.type); // text alert(body.type); // undefined:DOM 属性没有被创建,因为它不是一个标准的特性 </script> </body>
1
2
3
4
5
6
7
所有特性(包含非标准特性)都可以通过使用以下方法进行访问:
element.hasAttribute(name)
: 检查特性是否存在。element.getAttribute(name)
:获取这个特性值。element.setAttribute(name, value)
: 设置这个特性值。element.removeAttribute(name)
: 移除这个特性。
🌰 例子 / 使用特性:
<body>
<div id="elem" about="Elephant"></div>
<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant',读取
elem.setAttribute('Test', 123); // (2) 写入
alert( elem.outerHTML ); // (3) 查看特性是否在 HTML 中(在)
for (let attr of elem.attributes) { // (4) 列出所有
alert( `${attr.name} = ${attr.value}` );
}
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 特性的名称是大小写不敏感的。
- 可以将任何东西赋值给特性,但是这些东西会变成 字符串类型。所以这里的值为
"123"
。- 所有特性,包括自己设置的那个特性,在
outerHTML
中都是可见的。attributes
集合是可迭代对象,该对象将所有元素的特性(标准和非标准的)作为name
和value
属性存储在对象中。
# 属性与特性同步
当一个标准的特性被改变,对应的属性也会自动更新,(除了几个特例)反之亦然。
🌰 例子:
<input>
<script>
let input = document.querySelector('input');
// 特性 => 属性
input.setAttribute('id', 'id');
alert(input.id); // id(被更新了)
// 属性 => 特性
input.id = 'newId';
alert(input.getAttribute('id')); // newId(被更新了)
</script>
2
3
4
5
6
7
8
9
10
11
12
13
id
被修改为特性,对应的属性也会发生变化。反之亦然。
🌰 例子 / 例外:
<input>
<script>
let input = document.querySelector('input');
// 特性 => 属性
input.setAttribute('value', 'text');
alert(input.value); // text
// 这个操作无效,属性 => 特性
input.value = 'newValue';
alert(input.getAttribute('value')); // text(没有被更新!)
</script>
2
3
4
5
6
7
8
9
10
11
12
13
这个例子中:
- 改变特性值
value
会更新属性。- 但是属性的更改不会影响特性。意味着 特性的
value
会被保留在特性中。(之后想要原始值可用)
# DOM 属性的类型
DOM 属性不总是 字符串类型,但是大多是都是字符串。
🌰 例子 / checked
属性是布尔型:
<input id="input" type="checkbox" checked> checkbox
<script>
console.log(input.getAttribute('checked')); // 特性值是:空字符串
console.log(input.checked); // 属性值是:true
</script>
2
3
4
5
6
🌰 例子 / 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>
2
3
4
5
6
7
8
9
10
🌰 例子 / 即使一个 DOM 属性是字符串类型的,但它可能和 HTML 特性也是不同的,例如 href
路径 DOM 属性一直是一个 完整的 URL,即使该特性包含一个相对路径或者包含一个 #hash
。:
<a id="a" href="#hello">link</a>
<script>
// 特性
alert(a.getAttribute('href')); // #hello
// 属性
alert(a.href ); // http://site.com/page#hello 形式的完整 URL
</script>
2
3
4
5
6
7
8
所以要使用完整的路径
href
或者其他与 HTML 中所写的完全相同的特性,则可以使用getAttribute
。
# 非标准 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 document.querySelectorAll('[show-info]')) {
// 在字段中插入相应的信息
let field = div.getAttribute('show-info');
div.innerHTML = user[field];
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🌰 例子 / 设置元素的样式:
<style>
/* 样式依赖于自定义特性 "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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
使用特性值比使用特定的类名更好管理,可以轻松改变状态。
div.setAttribute('order-state', 'canceled');
1
自定义特性存在的问题:如果处于目的使用非标准特性,之后它被引入到了标准中并有了其自己的用途。HTML 语言是在不断发展的,并且更多的特性出现在了标准中,以满足开发者的需求。在这种情况下,自定义的属性可能会产生意料不到的影响。
为了避免冲突,保留以 data-*
开头命名的特性,供程序员使用。它们可在 dataset
属性中使用。
使用 data-*
特性是一种合法且安全的传递自定义数据的方式。不仅可以直接读取数据,还可以修改数据属性。
🌰 例子:
<body data-about="Elephants">
<script>
console.log(document.body.dataset.about); // Elephants
</script>
2
3
4
🌰 例子:
<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>
// 读取
console.log(order.dataset.orderState); // new
// 修改
order.dataset.orderState = "pending"; // (*)
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 总结
- 大多数情况下,最好使用 DOM 属性。仅当 DOM 属性无法满足开发需求,并且真的需要特性时,才使用特性。
- 需要一个非标准的特性。它应该以
data-
开头,使用dataset
获取、修改。
- 需要一个非标准的特性。它应该以
# 修改 DOM 文档
动态创建新元素并修改现有页面内容。
🌰 例子 / 向用户展示新的信息:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>
2
3
4
5
6
7
8
9
10
11
12
13
要使用 JavaScript 创建这个相同的 div
元素内容(CSS 样式已存在)。
# 创建元素
创建 DOM 元素节点的两种方法:
document.createElement(tag)
:用给定的标签创建一个新 元素节点:let div = document.createElement('div');
1document.createTextNode(text)
:用给定的文本创建一个文本节点:let textNode = document.createTextNode('Here I am');
1
# 完善元素内容
- 创建元素节点 ✅
- 设置类名
- 填充内容
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
2
3
4
5
# 插入元素
为了让 div
显示出来,需要将其插入到 document
文档中的某处。
使用特殊的方法 append
:可以在其他任何元素上调用 append
方法,以将另外一个元素放入到里面。
document.body.append(div)
div.append(anotherElement)
2
🌰 例子 / 完整的代码:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.append(div);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
对
document.body
调用了append
方法。
更多的插入元素的方法,对应不同的插入位置:
node.append()
在node
末尾 插入节点或字符串,node.prepend()
在node
开头 插入节点或字符串,node.before()
:在node
前面 插入节点或字符串,node.after()
—— 在node
后面 插入节点或字符串,node.replaceWith()
将node
替换为给定的节点或字符串。
🌰 例子:
<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
ol.before('before'); // 将字符串 "before" 插入到 <ol> 前面
ol.after('after'); // 将字符串 "after" 插入到 <ol> 后面
let liFirst = document.createElement('li');
liFirst.innerHTML = 'prepend';
ol.prepend(liFirst); // 将 liFirst 插入到 <ol> 的最开始
let liLast = document.createElement('li');
liLast.innerHTML = 'append';
ol.append(liLast); // 将 liLast 插入到 <ol> 的最末尾
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
最终列表为:
before
<ol id="ol">
<li>prepend</li>
<li>0</li>
<li>1</li>
<li>2</li>
<li>append</li>
</ol>
after
2
3
4
5
6
7
8
9
以上的插入元素的方法只能插入 带有内容节点 或者字符串内容。对于 HTML 代码的字符串,并不能被 浏览器解析,而是作为文本片段插入到页面。
通用的插入元素方法: elem.insertAdjacentHTML(where, html)
beforebegin
: 将html
插入到elem
前插入,afterbegin
:将html
插入到elem
开头,beforeend
:将html
插入到elem
末尾,afterend
: 将html
插入到elem
后。
第二个参数 html
,是 HTML 字符串,该字符串会被「作为 HTML」 插入。
🌰 例子:
<div id="div"></div>
<script>
div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
div.insertAdjacentHTML('afterend', '<p>Bye</p>');
</script>
2
3
4
5
结果:
<p>Hello</p>
<div id="div"></div>
<p>Bye</p>
2
3
衍生的兄弟方法:
elem.insertAdjacentText(where, text)
:语法一样,但是将text
字符串 作为文本 插入而不是作为 HTML,elem.insertAdjacentElement(where, elem)
语法一样,但是插入的是一个元素。
(对于这些 元素和文本,一般使用 append/prepend/before/after
更加方便)
# 移除节点
想要移除一个节点,可以使用 node.remove()
。
🌰 例子 / 将消息在一秒后移除:
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.append(div);
setTimeout(() => div.remove(), 1000);
</script>
2
3
4
5
6
7
8
# 移动节点
利用 插入元素的方法 after()
或者其他方法。
如果要将一个元素 移动 到另一个地方,则无需将其从原来的位置中删除。所有插入方法都会自动从旧位置删除该节点。
🌰 例子 / 交换元素的位置:
<div id="first">First</div>
<div id="second">Second</div>
<script>
// 无需调用 remove
second.after(first); // 获取 #second,并在其后面插入 #first
</script>
2
3
4
5
6
# 克隆节点
调用 elem.cloneNode(true)
来创建元素的一个深克隆:具有所有特性和子元素。
调用 elem.cloneNode(false)
,那克隆就不包括子元素。
🌰 例子:
<div class="alert" id="div">
<strong>Hi there!</strong> You've read an important message.
</div>
<script>
let div2 = div.cloneNode(true); // 克隆消息
div2.querySelector('strong').innerHTML = 'Bye there!'; // 修改克隆
div.after(div2); // 在已有的 div 后显示克隆
</script>
2
3
4
5
6
7
8
9
10
# 节点片段 DocumentFragment
这是一个特殊的 DOM 节点,用来传递节点列表的 包装器。可以向其 附加其他节点;当将其插入某个位置时,则会插入其内容。
🌰 例子 / 生成带有 <li>
列表项目的片段,并将它插入到 <ul>
当中:
<ul id="ul"></ul>
<script>
function getListContent() {
let fragment = new DocumentFragment();
for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
fragment.append(li);
}
return fragment;
}
ul.append(getListCount())
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
结果:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
2
3
4
5
一般
DocumentFragment
很少被显式使用。直接写成返回一个节点数组即可:function getListContent() { let result = []; for(let i=1; i<=3; i++) { let li = document.createElement('li'); li.append(i); result.push(li); } return result; }
1
2
3
4
5
6
7
8
9
10
11
# 总结
创建新节点的方法:
document.createElement(tag)
:用给定的tag
标签创建新的元素节点。document.createTextNode(value)
:创建文本节点。element.cloneNode(deep)
:克隆element
节点元素。
插入和移除节点的方法:
node.append()
— 在node
末尾插入,node.prepend()
— 在node
开头插入,node.before()
— 在node
之前插入,node.after()
— 在node
之后插入,node.replaceWith()
— 替换node
。node.remove()
— 移除node
。