前言:适合有基础的同学入门尝试 / 复习回忆。
本章先学习DOM操作的节点操作 ,事件处理下节再学。
※什么是DOM操作?——DOM(Document Object Model)是文档对象模型,它把HTML文档表示为节点树,让我们可以用 JavaScript 操作这个树状结构,从而动态地改变网页的内容和外观。
一、基础概念
在开始之前,我们需要理解几个关键概念:
-
DOM树:HTML文档被浏览器解析为一个树状结构,每个标签、属性、文本都是一个节点
-
节点类型:主要有元素节点①、属性节点②、文本节点③等
-
节点关系:父子(parent-child)、兄弟(sibling)关系
二、节点获取
1. 传统方法
- getElementById() 方法:通过元素的 id 属性值获取唯一的元素对象;
- getElementsByClassName() 方法:通过元素的 class 属性值获取元素对象,返回相应的元素集合;
- getElementsByName() 方法:通过元素的name属性值获取元素对象,返回相应的元素集合;
- getElementsByTagName() 方法:通过元素类型获取元素对象,返回相应的元素集合,如 document.getElementsByTagName("p") 获取所有段落元素;
// 通过ID获取
const element = document.getElementById('myId');
// 通过类名获取(返回HTMLCollection)
const elements = document.getElementsByClassName('myClass');
// 通过标签名获取(返回HTMLCollection)
const divs = document.getElementsByTagName('div');
// 通过name属性获取(常用于表单元素)
const inputs = document.getElementsByName('username');
其中,由于ID在HTML文档中是唯一的,所以document.getElementById()
可以直接定位唯一的一个DOM节点。
2. 现代方法(推荐)
// 查询单个元素(返回第一个匹配的元素)
const el = document.querySelector('.my-class');
// 查询多个元素(返回NodeList)
const els = document.querySelectorAll('div.note');
3. 特殊选择
// 获取文档根元素
const html = document.documentElement;
// 获取body元素
const body = document.body;
// 获取head元素
const head = document.head;
小案例1:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style></style>
</head>
<body>
<h1 id="MyName">Yuki</h1>
</body>
<script>
const nameElement = document.getElementById('MyName'); // 获取元素
nameElement.addEventListener('click', () => {
nameElement.style.color = 'blue';
nameElement.style.fontSize = '24px';
nameElement.style.fontWeight = 'bold';
nameElement.textContent = 'Yuki - 你点击了我!';
})
</script>
</html>
注意点:
这个案例中,我们发现在CSS中font-size
这样的名称,却在JavaScript中不是有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize
小案例2:
跟id的唯一性不同的是,页面中的多个元素可能使用相同的 class 属性值,这时候获取这些元素时可以使用 document.getElementsByClassName() 方法,前面说过,它会返回一个由这些元素组成的集合!
<!DOCTYPE html>
<html>
<head>
<title>getElementsByClassName案例</title>
<style>
.highlight {
padding: 10px;
margin: 5px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div class="highlight">第一个高亮元素</div>
<div class="highlight">第二个高亮元素</div>
<div class="highlight">第三个高亮元素</div>
<button id="changeColorBtn">改变所有高亮元素颜色</button>
<script>
const changeBtn = document.getElementById('changeColorBtn');
changeBtn.addEventListener('click', function() {
// 获取所有class为highlight的元素
const highlights = document.getElementsByClassName('highlight');
// 遍历并修改每个元素的样式
for (let i = 0; i < highlights.length; i++) {
highlights[i].style.backgroundColor = getRandomColor();
highlights[i].style.color = 'white';
highlights[i].style.fontWeight = 'bold';
}
});
// 生成随机颜色函数(这个函数可以暂时不管,只是为了辅助理解,可以自行搜索代码解析)
function getRandomColor() {
// letters包含了十六进制颜色中可能用到的所有字符:0-9 数字和 A-F 字母(共 16 个字符)
const letters = '0123456789ABCDEF';
let color = '#'; // 十六进制颜色的前缀标识
// 使用 for 循环执行 6 次(因为十六进制颜色由 6 个字符组成)
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color; // 返回生成的随机颜色,如 #3A7F9D
}
</script>
</body>
</html>
※代码解析:
-
我们创建了3个具有相同class="highlight"的div元素
-
点击按钮时,使用
getElementsByClassName
获取所有高亮元素 -
遍历返回的HTMLCollection,为每个元素设置随机背景色
-
注意HTMLCollection是"动态的",会实时反映DOM变化(点击会实时变化颜色,如下图)
小案例3:
注意:getElementsByClassName
和getElementsByTagName
返回的是动态集合(HTMLCollection),而querySelectorAll
返回的是静态集合(NodeList)。
案例2感受到了getElementsByClassName
的动态,那么这个静态怎么理解?来个小案例感受下对比吧!
<!DOCTYPE html>
<html>
<head>
<title>动态vs静态集合</title>
</head>
<body>
<div class="box">盒子1</div>
<div class="box">盒子2</div>
<button id="addBoxBtn">添加盒子</button>
<button id="showBoxCountBtn">显示盒子数量</button>
<script>
const addBoxBtn = document.getElementById('addBoxBtn');
const showBoxCountBtn = document.getElementById('showBoxCountBtn');
let boxCount = 3; // 初始盒子数量
// 两种方式获取元素
const boxesHTMLCollection = document.getElementsByClassName('box');
console.log('初始HTMLCollection长度:', boxesHTMLCollection.length);
const boxesNodeList = document.querySelectorAll('.box');
console.log('初始NodeList长度:', boxesNodeList.length);
// 添加新盒子
addBoxBtn.addEventListener('click', () => {
const newBox = document.createElement('div'); // 这个创建节点在下面会讲
newBox.className = 'box';
newBox.textContent = `盒子${boxCount++}`;
document.body.appendChild(newBox);
});
// 显示盒子数
showBoxCountBtn.addEventListener('click', () => {
console.log('当前HTMLCollection长度:', boxesHTMLCollection.length);
console.log('当前NodeList长度:', boxesNodeList.length);
alert(`HTMLCollection长度: ${boxesHTMLCollection.length}, NodeList长度: ${boxesNodeList.length}`);
// 注意:boxesHTMLCollection是动态的,会自动更新
// boxesNodeList是静态的,不会自动更新
});
</script>
</body>
</html>
先看运行结果感受下:
点击添加盒子,随便点击几次,然后点击显示盒子数量,可以看到:
关键区别:
-
getElementsByClassName
返回的HTMLCollection是动态的,会随DOM变化自动更新,每次添加新元素后,length会自动增加 -
querySelectorAll
返回的NodeList是静态的,不会反映之后的DOM变化 -
在需要实时获取最新元素集合时,使用
getElementsByClassName
更合适
三、节点创建
1. 创建元素节点
// 创建一个div元素
const newDiv = document.createElement('div');
// 创建一个文本节点
const newText = document.createTextNode('你好,世界!');
// 创建一个注释节点
const newComment = document.createComment('这是一个注释');
这一块上面的案例3就有涉及到,可以回过头去看看~~
2. 克隆节点
const original = document.querySelector('.original');
const cloned = original.cloneNode(true); // true表示深克隆(包括子节点)
深入了解可到:插入DOM - JavaScript教程 - 廖雪峰的官方网站
四、节点修改
1. 修改内容
// 方法一:修改HTML内容(会解析HTML标签)
element.innerHTML = '<strong>新内容</strong>';
// 方法二:修改文本内容(不会解析HTML标签)
element.innerText = '纯文本内容';
element.textContent = '纯文本内容';
// 修改value(表单元素)
input.value = '新的值';
注意1:其中,innerText 属性将内容当作文本处理,而 innerHTML 属性则将内容解析为 HTML 代码,可以使用标签生成页面元素。
怎么理解这句话?来个小案例感受下:
小案例1:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<p id="p1">段落一</p>
<p id="p2">段落二</p>
</body>
</html>
<script>
var p1 = document.getElementById("p1");
var p2 = document.getElementById("p2");
var s = "<b>加粗文本</b>";
p1.innerText += s;
p2.innerHTML += s;
</script>
可以看到,段落一是使用的 innerText 去获取的,不管你<b>标签的用法,当纯文本。
而段落二是 innerHTML ,会把<b>标签识别为html代码。
注意2:为什么方式二有两种: innerText
和 textContent
?——核心区别:是否返回隐藏元素的文本(另外注意IE<9不支持textContent
)
怎么理解?
innerText
:获取元素文本时,会忽略页面中被 CSS 隐藏的元素(比如设置了display: none
或visibility: hidden
的元素),只返回用户在页面上能实际看到的文本内容。textContent
:则会返回元素中所有文本内容,无论这些文本所在的元素是否被隐藏,包括通过 CSS 隐藏的元素中的文本。
照旧,来个案例感受:
小案例2:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div id="test">
可见文本
<span style="display: none;">隐藏文本</span>
</div>
</body>
</html>
<script>
var test = document.getElementById("test").textContent;
console.log(test);
var test2 = document.getElementById("test").innerText;
console.log(test2);
</script>
2. 修改属性
// 设置属性
element.setAttribute('id', 'newId');
// 获取属性
const id = element.getAttribute('id');
// 移除属性
element.removeAttribute('id');
// 检查是否有某属性
const hasAttr = element.hasAttribute('id');
// class操作
element.classList.add('new-class');
element.classList.remove('old-class');
element.classList.toggle('active');
3. 修改样式
// 直接修改style属性
element.style.color = 'red';
element.style.backgroundColor = '#f0f0f0';
// 批量修改样式
Object.assign(element.style, {
color: 'blue',
fontSize: '16px',
display: 'none'
});
五、节点删除
1. 移除节点
// 方法1:通过父节点移除子节点
// 解析:要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉
const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.removeChild(child);
// 方法2:直接移除自己(较新方法)
child.remove();
2. 替换节点
const oldNode = document.getElementById('old');
const newNode = document.createElement('div');
newNode.textContent = '我是新节点';
// 通过父节点替换
oldNode.parentNode.replaceChild(newNode, oldNode);
// 或者使用replaceWith方法
oldNode.replaceWith(newNode);
六、节点关系
1. 父子关系
// 获取父节点
const parent = child.parentNode; // 或 parentElement
// 获取所有子节点(包括文本节点等)
const allChildren = parent.childNodes;
// 获取子元素(仅元素节点)
const children = parent.children;
// 第一个子节点
const firstChild = parent.firstChild;
// 最后一个子节点
const lastChild = parent.lastChild;
2. 兄弟关系
// 上一个兄弟节点
const prevSibling = element.previousSibling;
// 下一个兄弟节点
const nextSibling = element.nextSibling;
// 上一个兄弟元素节点
const prevElementSibling = element.previousElementSibling;
// 下一个兄弟元素节点
const nextElementSibling = element.nextElementSibling;
七、实践案例:
练习1:创建一个评论列表
<!DOCTYPE html>
<html>
<head>
<title>评论列表</title>
<style>
.comment {
border: 1px solid #ddd;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="comments-container"></div>
<button id="add-comment-btn">添加评论</button>
<script>
const container = document.getElementById('comments-container');
const btn = document.getElementById('add-comment-btn');
let counter = 1;
btn.addEventListener('click', () => {
// 创建评论元素
const comment = document.createElement('div');
comment.className = 'comment';
// 创建评论内容
const content = document.createElement('p');
content.textContent = `这是第${counter++}条评论`;
// 创建删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.addEventListener('click', () => {
comment.remove();
});
// 组装评论
comment.appendChild(content);
comment.appendChild(deleteBtn);
// 添加到容器
container.appendChild(comment);
});
</script>
</body>
</html>
八、尾声:
JavaScript 中的 DOM 操作——面向初学者的全面指南