🍥 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) // HTMLBodyElement1toString方法: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) // true1
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。
