🎯 课程目标
完成本课程后,你将能够:
- 理解DOM(文档对象模型)的概念和结构
- 掌握选择元素的各种方法
- 能够创建、修改和删除DOM元素
- 理解元素属性和样式的操作方法
- 能够动态更新页面内容
📖 DOM概述
DOM(Document Object Model,文档对象模型)是HTML和XML文档的编程接口。它将文档表示为树形结构,每个节点都是文档的一部分,通过DOM可以动态地访问和更新文档内容、结构和样式。
✨ DOM树结构
- Document节点:整个文档的根节点
- Element节点:HTML/XML元素
- Text节点:元素内的文本内容
- Attribute节点:元素的属性
- Comment节点:HTML注释
🔍 选择元素
在进行DOM操作之前,首先需要选中目标元素。JavaScript提供了多种选择元素的方法。
方法 1:通过ID选择
// 通过ID获取单个元素(最常用)
const header = document.getElementById('main-header');
console.log(header); // 返回元素或null
// 注意:如果元素不存在,返回null
方法 2:通过CSS选择器选择
// querySelector:返回匹配的第一个元素
const firstButton = document.querySelector('.btn');
const navLink = document.querySelector('#nav a');
// querySelectorAll:返回所有匹配元素的NodeList
const allButtons = document.querySelectorAll('.btn');
const listItems = document.querySelectorAll('ul > li');
// 可以使用任意CSS选择器
const inputs = document.querySelectorAll('input[type="text"]');
const activeItems = document.querySelectorAll('.item.active');
// NodeList转为数组
const buttonsArray = Array.from(allButtons);
const buttonsArray2 = [...allButtons];
方法 3:其他选择方法
// 通过标签名选择(返回HTMLCollection)
const paragraphs = document.getElementsByTagName('p');
// 通过类名选择(返回HTMLCollection)
const cards = document.getElementsByClassName('card');
// 通过Name属性选择(返回NodeList)
const radioButtons = document.getElementsByName('gender');
// 选择表单元素
const form = document.forms['myForm']; // 通过name选择表单
const formElements = form.elements; // 获取表单内所有元素
// 获取body元素
const body = document.body;
const head = document.head;
const html = document.documentElement;
🎨 遍历DOM树
除了选择特定元素,有时还需要在DOM树中遍历查找。
遍历节点关系
const item = document.querySelector('.item');
// 父节点/父元素
item.parentNode; // 父节点(可能是文档节点)
item.parentElement; // 父元素(如果不是元素返回null)
// 子节点/子元素
item.childNodes; // 所有子节点(NodeList)
item.children; // 所有子元素(HTMLCollection)
item.firstChild; // 第一个子节点
item.firstElementChild; // 第一个子元素
item.lastChild; // 最后一个子节点
item.lastElementChild; // 最后一个子元素
// 兄弟节点
item.previousSibling; // 前一个兄弟节点
item.nextSibling; // 后一个兄弟节点
item.previousElementSibling; // 前一个兄弟元素
item.nextElementSibling; // 后一个兄弟元素
✏️ 创建和修改元素
使用JavaScript动态创建、修改和删除DOM元素。
创建新元素
// 创建新元素
const newDiv = document.createElement('div');
const newLink = document.createElement('a');
const newImg = document.createElement('img');
// 设置元素内容
newDiv.textContent = '这是新创建的div'; // 纯文本(安全)
newDiv.innerHTML = '<span>HTML内容</span>'; // HTML(注意XSS风险)
// 创建文本节点(更安全的方式)
const textNode = document.createTextNode('纯文本内容');
newDiv.appendChild(textNode);
// 创建片段(批量插入时性能更好)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 5; i++) {
const item = document.createElement('div');
item.textContent = '项' + i;
fragment.appendChild(item);
}
document.body.appendChild(fragment);
插入元素
const parent = document.querySelector('.container');
const newItem = document.createElement('div');
newItem.textContent = '新项目';
// 插入到末尾
parent.appendChild(newItem);
// 插入到开头
parent.prepend(newItem);
// 插入到某个元素之前
parent.insertBefore(newItem, parent.firstChild);
// 高级插入方法(更灵活)
parent.append('文本节点'); // 插入到末尾
parent.prepend('文本节点'); // 插入到开头
// 在指定元素前/后插入
const reference = parent.querySelector('.reference');
reference.after(newItem); // 在reference后插入
reference.before(newItem); // 在reference前插入
// replaceWith替换元素
reference.replaceWith(newItem);
删除和替换元素
const item = document.querySelector('.item');
// 删除元素
item.remove(); // 现代方法
item.parentNode.removeChild(item); // 传统方法(兼容旧浏览器)
// 清空元素内容
item.innerHTML = '';
while (item.firstChild) {
item.removeChild(item.firstChild);
}
// 替换元素
const newItem = document.createElement('div');
newItem.textContent = '替换内容';
item.replaceWith(newItem);
// 克隆元素(深克隆包含所有子节点)
const clonedItem = item.cloneNode(true); // true表示深克隆
// 浅克隆(只复制自己)
const shallowClone = item.cloneNode(false);
🎭 操作属性和样式
操作元素属性
const link = document.querySelector('a');
// 获取属性
console.log(link.href); // 获取href属性
console.log(link.getAttribute('href')); // 同样效果
// 设置属性
link.href = 'https://example.com';
link.setAttribute('target', '_blank');
// 移除属性
link.removeAttribute('target');
// 检查属性是否存在
if (link.hasAttribute('href')) {
console.log('链接有href属性');
}
// data-*自定义属性
const card = document.querySelector('.card');
card.dataset.id = '123'; // 设置 data-id="123"
card.dataset.userName = '张三'; // 设置 data-user-name="张三"
console.log(card.dataset.id); // 读取
console.log(card.dataset.userName); // 读取(自动转为驼峰命名)
// 类名操作(推荐方式)
const box = document.querySelector('.box');
box.classList.add('active'); // 添加类
box.classList.remove('hidden'); // 移除类
box.classList.toggle('selected'); // 切换类(有则移除,无则添加)
box.classList.contains('active'); // 检查是否包含类
// 直接操作className(不推荐)
box.className = 'new-class";
操作内联样式
const box = document.querySelector('.box');
// 通过style属性操作内联样式
box.style.color = 'red';
box.style.backgroundColor = '#f0f0f0';
box.style.fontSize = '16px';
box.style.cssText = 'color: red; background: blue;'; // 批量设置
// 获取计算后的样式(只读)
const computedStyle = window.getComputedStyle(box);
console.log(computedStyle.color); // rgb(255, 0, 0)
console.log(computedStyle.width); // "100px"
// 驼峰命名 vs 连字符命名
box.style.marginTop = '20px'; // 驼峰
box.style['margin-top'] = '20px'; // 字符串(连字符)
📝 实战:动态待办列表
让我们综合运用DOM操作知识,创建一个动态的待办列表应用。
<!-- HTML结构 -->
<div class="todo-app">
<h2>待办事项</h2>
<div class="input-group">
<input type="text" id="todoInput" placeholder="添加新任务...">
<button id="addBtn">添加</button>
</div>
<ul id="todoList"></ul>
</div>
<script>
const todoInput = document.getElementById('todoInput');
const addBtn = document.getElementById('addBtn');
const todoList = document.getElementById('todoList');
// 添加待办事项
function addTodo() {
const text = todoInput.value.trim();
if (!text) return;
// 创建列表项
const li = document.createElement('li');
// 创建任务文本
const span = document.createElement('span');
span.textContent = text;
// 创建删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.className = 'delete-btn';
// 添加点击切换完成状态
span.addEventListener('click', () => {
li.classList.toggle('completed');
});
// 删除待办事项
deleteBtn.addEventListener('click', () => {
li.remove();
});
// 组装元素
li.appendChild(span);
li.appendChild(deleteBtn);
todoList.appendChild(li);
// 清空输入框
todoInput.value = '';
}
// 绑定事件
addBtn.addEventListener('click', addTodo);
todoInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addTodo();
});
</script>
<style>
.todo-app { max-width: 400px; margin: 20px auto; }
.todo-app li {
display: flex;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-app li.completed span {
text-decoration: line-through;
color: #999;
}
.delete-btn {
background: #ff4444;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
}
</style>
⚠️ 常见问题
| 问题 | 原因分析 | 解决方案 |
|---|---|---|
| 获取元素返回null | 元素不存在或选择器错误 | 检查元素是否存在,确认选择器正确 |
| innerHTML导致XSS | 直接插入用户输入的HTML | 使用textContent或对输入进行转义 |
| 修改样式不生效 | 优先级问题或选择器 specificity | 使用更具体的选择器或important |
| 动态添加的元素无法响应事件 | 事件绑定在静态元素上 | 使用事件委托或重新绑定 |
| HTMLCollection和NodeList区别 | 前者实时更新,后者静态 | 根据需求选择,必要时转为数组 |
✅ 课后练习
练习要求:请独立完成以下练习任务:
- 练习 1:创建一个图片画廊,点击图片可以放大显示
- 练习 2:实现一个表格,支持按列排序功能
- 选做练习:创建一个动态树形菜单,支持展开和收起