# JavaScript教程 - 16 DOM
继续讲解 DOM。
# 16.5 文本节点
在前面也说了文本也是节点。
下面再举个例子:
<body>
<div id="my-div">For技术栈</div>
<script>
const element = document.getElementById('my-div'); // 获取到div元素
console.log(element.firstChild); // 获取div下的第一个节点,也就是文本节点:For技术栈
</script>
</body>
2
3
4
5
6
7
8
- 上面就获取到了文本节点。
但是通过获取文本节点再修改文本,太麻烦了,我们一般不这样做,我们只需要获取到文本节点的父节点进行操作就可以了。
所以在上面的代码中,获取 div 元素,就可以修改 div 中的文本了,有三种方式可以获取和修改元素中的文本内容:
- element.textContent
- element.innerText
- element.innerHTML
举个栗子:
<body>
<div id="box1">For技术栈</div>
<div id="box2">For技术栈</div>
<div id="box3">For技术栈</div>
<script>
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');
console.log(box1.textContent); // For技术栈
console.log(box2.innerText); // For技术栈
console.log(box3.innerHTML); // For技术栈
box1.textContent = 'For技术栈1';
box2.innerText = 'For技术栈2';
box3.innerHTML = 'For技术栈3';
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 在上面的代码中,可以通过上面三种方式来获取和修改 div 中的文本内容。
问题来了,有什么区别?
在看一个例子:
<body>
<div id="box1">
<span>For技术栈</span>
</div>
<div id="box2">
<span>For技术栈</span>
</div>
<div id="box3">
<span>For技术栈</span>
</div>
<script>
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');
console.log(box1.textContent); //
console.log(box2.innerText); //
console.log(box3.innerHTML); // <span>For技术栈</span>
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
textContent
和innerText
获取不到 html 标签,innerText
就是纯文本,textContent
会包括换行符。innerHTML
可以获取到 html 标签内容。
执行结果如下:
再看一下修改文本内容的区别:
<body>
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
<script>
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');
box1.textContent = '<b>For技术栈1</b>';
box2.innerText = '<b>For技术栈2</b>';
box3.innerHTML = '<b>For技术栈3</b>';
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
显示如下:
可以看到 textContent
和 innerText
会对文本内容进行转义(例如,大于号转换为 >
),将 html 内容按照文本内容来显示,而 innerHTML
会将 html 内容渲染后显示,但是 innerHTML
这种方式容易存在 xss
注入风险,因为如果文本内容是用户自定义的,用户可以在文本内容中添加 html 内容(插入不和谐的内容,你的网站吃不了兜着走),而且可以添加 <script>
标签引入第三方的 js 脚本去执行,这就很危险了,所以使用 innerHTML
要确保内容是自己的。
textContent
和 innerText
还有一个区别就是 textContent
获取标签中的内容不会考虑 CSS 样式,而 innerText
会考虑 CSS 样式。
举个栗子:
<body>
<div id="box1">
<span style="display: none;">For技术栈</span>
</div>
<div id="box2">
<span style="display: none;">For技术栈</span>
</div>
<div id="box3">
<span style="display: none;">For技术栈</span>
</div>
<script>
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');
console.log(box1.textContent); // 'For技术栈'
console.log(box2.innerText); // ''
console.log(box3.innerHTML); // <span style="display: none;">For技术栈</span>
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 将内容隐藏后,
innerText
是获取不到内容的;通过innerText
获取内容的时候,会触发网页重排(Reflow),也就是重新结算 CSS 样式,所以textContent
的性能是要好一点的。
# 16.6 属性节点
下面来讲解 DOM 中的属性节点。标签中各个属性也是一个个的节点对象,但是和文本节点对象类似,我们没必要在操作属性的时候获取到属性节点再进行属性的操作,只需要获取到标签元素,就可以操作属性了。
属性的操作有两种方式:
- 元素对象.属性
- 元素对象.
# 1 元素对象.属性
举个栗子:
<body>
<input id="username" name="username" value="foooor" class="input-class" />
<script>
const usernameEle = document.getElementById('username');
// 获取属性
console.log(usernameEle.type); // text
console.log(usernameEle.id); // username
console.log(usernameEle.name); // username
console.log(usernameEle.value); // foooor
console.log(usernameEle.className); // 获取class,输出:input-class
// 设置属性
usernameEle.value = 'www.foooor.com'
usernameEle.className = 'new-class'; // 设置class
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
可以通过
元素对象.属性
来获取和设置属性。上面修改了
value
的值,那么文本框的值就会发生变化了。但是有一个属性比较例外,需要注意一下,就是 class ,获取和设置 class 需要使用 className 。
在 HTML 中,有一些属性是布尔属性,例如 disabled
、checked
、readonly
等。这些属性存在即生效,只要属性出现在标签中,无论值是什么,都表示"真"(true)。这种属性在获取和设置的时候,值为 boolean 类型。
举个栗子:
<body>
<input disabled id="username" name="username" value="foooor" class="input-class" />
<script>
const usernameEle = document.getElementById('username');
// 获取属性
console.log(usernameEle.disabled); // true,表示不可用
// 设置属性
usernameEle.disabled = false; // 设置为可用
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
# 2 attribute
获取和设置属性还可以通过 getAttribute('属性名')
和 setAttribute('属性名', 值)
。
举个栗子:
<body>
<input id="username" name="username" value="foooor" class="input-class" />
<script>
const usernameEle = document.getElementById("username");
// 获取属性
console.log(usernameEle.getAttribute('id')); // username
console.log(usernameEle.getAttribute('name')); // username
console.log(usernameEle.getAttribute('value')); // foooor
console.log(usernameEle.getAttribute('class')); // 获取class,输出:input-class
// 设置属性
usernameEle.setAttribute('value', 'www.foooor.com');
usernameEle.setAttribute ('class', 'new-class');
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 通过 attribute 的方式,
class
属性不用特殊对待。
但是对于布尔属性需要注意一下,按照下面的方式来设置:
// 禁用,值为任何值都为true
usernameEle.setAttribute('disabled', 'disable');
// 启用,移除属性
usernameEle.removeAttribute('disabled');
2
3
4
- 如果通过
setAttribute('disabled', false);
来设置,会被转换为disable="false"
,disable="xxxx"
结果都是true
的,元素依旧被禁用
# 16.7 事件处理
什么是事件?
DOM 中的事件是网页与用户互动的核心机制之一。它允许我们监听用户的操作(如点击、键盘输入、鼠标移动等),并在事件发生时作出响应。
事件处理有三种方式:
方式一:HTML 属性方式(不推荐)
举个栗子:
<body>
<button onclick="alert('你最帅!')">点击我</button>
</body>
2
3
- 在上面的代码中,当点击按钮后,会触发 onclick,执行 onclick 的代码。
方式二:DOM 属性方式
<body>
<button id="my-button">点击我</button>
<script>
const btn = document.getElementById("my-button");
btn.onclick = function () {
alert('你最帅!');
};
</script>
</body>
2
3
4
5
6
7
8
9
10
- 上面先获取到按钮元素,然后给按钮的 onclick 属性设置事件处理的方法,当点击按钮后,就会回调该方法。
- 这种方式,同一个事件只能绑定一个事件处理函数,多次绑定会覆盖前面的。
方式三:标准事件监听
<body>
<button id="my-button">点击我</button>
<script>
const btn = document.getElementById("my-button");
// 绑定一个点击事件
btn.addEventListener("click", function () {
alert("你最帅!");
});
// 再绑定一个点击事件
btn.addEventListener("click", function () {
alert("你最牛逼!");
});
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 首先获取到按钮元素,然后通过
addEventListener
给按钮添加事件监听器,第一个参数是事件的名称click
表示单击,不同的事件有不同的名称,第二个是事件触发后的回调方法。 - 这种方式一个事件可以添加多次回调,可以触发多个回调方法。
事件有很多种,后面再讲,现在先记住点击事件,以及事件的处理。
# 16.8 文档的加载
在前学习 DOM 的时候,编写 JavaScript 代码的时候,都是将 <script>
标签写在 HTML 元素的后面,这是为什么呢?
这是因为网页在加载的时候,是从上到下加载的,如果我们将 <script>
标签写在前面,那么在执行 JS 脚本的时候,DOM 元素都还没有被加载,那么通过 document.getElementById("my-button")
来获取元素是获取不到的。所以写在后面,等加载 JS 并执行的时候,前面的内容已经被加载了,就能获取到了。
问题来了, <script>
标签能否写在上面,位置随便呢?
可以,只需要让页面加载完成,JS 代码再执行就可以了。
# 1 window.load
可以将代码写到 window.load
的回调方法中,window.onload
事件会等到页面所有资源加载完成后才执行,例如会等到图片加载完成,页面中还有其他的 iframe 的话,也会等到 iframe 的内容加载完成。
举个栗子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>For技术栈</title>
<script>
window.onload = function() {
console.log("页面所有资源加载完成");
const btn = document.getElementById("my-button");
btn.onclick = () => alert("按钮被点击了");
};
</script>
</head>
<body>
<button id="my-button">点击我</button>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 在上面的代码中,在
window.onload
事件回调方法中,就可以获取到 DOM 元素了。
还可以使用 addEventListener
来绑定:
window.addEventListener("load", function () {
console.log("页面所有资源加载完成");
const btn = document.getElementById("my-button");
btn.onclick = () => alert("按钮被点击了");
});
2
3
4
5
# 2 DOMContentLoaded
DOMContentLoaded
事件在文档结构(DOM)加载完成后触发,不必等图片、CSS 加载完成。
document.addEventListener("DOMContentLoaded", function () {
const btn = document.getElementById("my-button");
btn.onclick = () => alert("按钮被点击了");
});
2
3
4
# 3 defer
我们可以将 JS 代码放在单独的 JS文件中,然后使用 <script>
引入,并添加 defer
属性(推荐现代做法之一)。defer
会让脚本延迟到整个 HTML 解析完成后再执行,等价于 DOMContentLoaded 前自动执行。
举个栗子:
首先在 JS 文件中编写代码,例如 index.js
:
const btn = document.getElementById("my-button");
btn.onclick = () => alert("按钮被点击了");
2
然后在 HTML 页面引入 JS 文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>For技术栈</title>
<!-- 引入js,并添加defer -->
<script defer src="./script/index.js"></script>
</head>
<body>
<button id="my-button">点击我</button>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
- 通过
<script>
标签引入,并添加defer
属性。
# 4 模块化脚本
使用模块化 <script type="module">
(现代推荐),模块化脚本会自动延迟执行,相当于默认带 defer
行为,适用于使用 ES6 模块语法的现代开发。
举个栗子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>For技术栈</title>
<script type="module">
const btn = document.getElementById("my-button");
btn.onclick = () => alert("模块化脚本按钮");
</script>
</head>
<body>
<button id="my-button">点击我</button>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
标签添加type="module"
。
# 16.9 练习
上面讲到了很多 DOM 的操作,下面做一个联系。
点击上一张和下一张,实现多张图片的切换。
如下图:
实现起来也是比较简单的,首先创建一个数组,在数组中存储所有图片的 url,然后监听两个按钮的点击事件,在点击按钮的时候,从数组中获取 url,修改图片标签的 src 属性即可。
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>For技术栈</title>
<style>
#gallery {
text-align: center;
margin-top: 50px;
}
img {
width: 400px;
height: 240px;
}
button {
margin: 20px;
}
</style>
<script>
window.onload = function () {
// 获取各个元素
const imgElement = document.getElementById("image"); // 图片
const prevButton = document.getElementById("prev"); // 上一张
const nextButton = document.getElementById("next"); // 下一张
// 图片 URL 数组
const imageUrls = [
'./image/html5.webp',
'./image/css3.webp',
'./image/javascript.webp',
'./image/git.webp',
'./image/nginx.webp'
];
let currentIndex = 0; // 当前显示图片的索引
// 上一张按钮
prevButton.addEventListener("click", function () {
currentIndex--;
// 防止越界
if (currentIndex < 0) {
currentIndex = 0;
}
imgElement.src = imageUrls[currentIndex]; // 修改图片
});
// 下一张按钮
nextButton.addEventListener("click", function () {
currentIndex++;
// 防止越界
if (currentIndex >= imageUrls.length) {
currentIndex = imageUrls.length - 1;
}
imgElement.src = imageUrls[currentIndex]; // 修改图片
});
};
</script>
</head>
<body>
<div id="gallery">
<img id="image" src="./image/html5.webp" alt="图片" />
<br />
<button id="prev">上一张</button>
<button id="next">下一张</button>
</div>
</body>
</html>
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
- 在切换图片的时候,切换图片的 url 即可。