DOM(文档对象模型)详解:前端开发的核心基础
什么是 DOM?
文档对象模型(Document Object Model,简称 DOM) 是 HTML 和 XML 文档的编程接口。它通过将文档的结构以对象树的形式存储在内存中,将网页与脚本或编程语言连接起来。
简单来说,DOM 就是浏览器将 HTML 文档解析成的一个树状结构,每个 HTML 元素都是树中的一个节点(Node),我们可以通过 JavaScript 等脚本语言来操作这些节点,从而改变网页的结构、样式和内容。
DOM 的核心概念
DOM 树结构
DOM 使用逻辑树的形式来表示文档。树的每个分支末端都是一个节点,每个节点都包含对象。例如,考虑如下 HTML 文档:
<html lang="zh-CN">
<head>
<title>我的文档</title>
</head>
<body>
<h1>标题</h1>
<p>段落内容</p>
</body>
</html>
其对应的 DOM 树结构如下:
html
├── head
│ └── title
│ └── "我的文档"
└── body
├── h1
│ └── "标题"
└── p
└── "段落内容"
DOM 节点类型
DOM 树中的每个节点都有特定的类型,常见的节点类型包括:
- 元素节点(Element Node):HTML 标签,如
<div>、<p>、<span>等 - 文本节点(Text Node):元素内的文本内容
- 属性节点(Attribute Node):元素的属性,如
class、id等 - 文档节点(Document Node):整个文档的根节点
- 注释节点(Comment Node):HTML 注释
DOM 和 JavaScript
虽然 DOM 不是 JavaScript 语言的一部分,但它通常与 JavaScript 一起使用。DOM 提供了访问和操作网页的标准接口,而 JavaScript 则提供了操作 DOM 的能力。
基本示例
// 获取所有段落元素
const paragraphs = document.querySelectorAll("p");
// 访问第一个段落
console.log(paragraphs[0].nodeName); // 输出: "P"
// 修改第一个段落的内容
paragraphs[0].textContent = "新的段落内容";
// 添加样式
paragraphs[0].style.color = "red";
常用的 DOM 操作方法
1. 选择元素
querySelector 和 querySelectorAll
// 选择第一个匹配的元素
const element = document.querySelector(".my-class");//class为my-class的第一个元素
const elementById = document.querySelector("#my-id");//id为my-id的元素
// 选择所有匹配的元素(返回 NodeList)
const elements = document.querySelectorAll("p");
getElementById、getElementsByClassName、getElementsByTagName
// 通过 ID 获取元素
const element = document.getElementById("my-id");
// 通过类名获取元素集合(返回 HTMLCollection)
const elements = document.getElementsByClassName("my-class");
// 通过标签名获取元素集合
const paragraphs = document.getElementsByTagName("p");
2. 创建和添加元素
// 创建新元素
const newDiv = document.createElement("div");
newDiv.textContent = "新创建的元素";
newDiv.className = "new-class";
// 添加到页面
const parent = document.querySelector(".container");
parent.appendChild(newDiv);
// 插入到指定位置
const referenceElement = document.querySelector(".reference");
parent.insertBefore(newDiv, referenceElement);
3. 修改元素内容
const element = document.querySelector("#my-element");
// 修改文本内容(推荐,更安全)
element.textContent = "新的文本内容";
// 修改 HTML 内容(注意 XSS 风险)
element.innerHTML = "<strong>加粗文本</strong>";
// 修改属性
element.setAttribute("class", "new-class");
element.id = "new-id";
4. 删除元素
const element = document.querySelector("#to-remove");
// 方法1:使用 remove() 方法(现代浏览器)
element.remove();
// 方法2:使用 removeChild() 方法
const parent = element.parentNode;
parent.removeChild(element);
5. 修改样式
const element = document.querySelector("#my-element");
// 直接修改 style 属性
element.style.color = "red";
element.style.backgroundColor = "blue";
element.style.fontSize = "16px";
// 添加/移除类名(推荐方式)
element.classList.add("active");
element.classList.remove("inactive");
element.classList.toggle("highlight");
DOM 事件处理
DOM 事件是用户与网页交互时触发的动作,如点击、鼠标移动、键盘输入等。
添加事件监听器
const button = document.querySelector("#my-button");
// 方法1:使用 addEventListener(推荐)
button.addEventListener("click", function(event) {
console.log("按钮被点击了");
console.log("事件对象:", event);
});
// 方法2:使用箭头函数
button.addEventListener("click", (event) => {
console.log("按钮被点击了");
});
// 方法3:使用命名函数
function handleClick(event) {
console.log("按钮被点击了");
}
button.addEventListener("click", handleClick);
移除事件监听器
// 必须使用相同的函数引用
button.removeEventListener("click", handleClick);
事件传播
DOM 事件有两个阶段:
- 捕获阶段(Capture Phase):事件从文档根节点向下传播到目标元素
- 冒泡阶段(Bubble Phase):事件从目标元素向上传播到文档根节点
// 阻止事件冒泡
button.addEventListener("click", function(event) {
event.stopPropagation();
console.log("事件不会继续向上传播");
});
// 阻止默认行为
const link = document.querySelector("a");
link.addEventListener("click", function(event) {
event.preventDefault();
console.log("链接的默认跳转行为被阻止");
});
常用事件类型
- 鼠标事件:
click、dblclick、mousedown、mouseup、mousemove、mouseover、mouseout - 键盘事件:
keydown、keyup、keypress - 表单事件:
submit、change、input、focus、blur - 窗口事件:
load、resize、scroll
DOM 遍历
访问父节点和子节点
const element = document.querySelector("#my-element");
// 父节点
const parent = element.parentNode;
// 子节点
const children = element.childNodes; // 包括文本节点
const childrenElements = element.children; // 只包括元素节点
// 第一个和最后一个子节点
const firstChild = element.firstChild;
const lastChild = element.lastChild;
// 第一个和最后一个子元素
const firstElementChild = element.firstElementChild;
const lastElementChild = element.lastElementChild;
// 兄弟节点
const nextSibling = element.nextSibling;
const previousSibling = element.previousSibling;
实际应用示例
示例1:动态创建列表
// HTML 结构
// <ul id="my-list"></ul>
const list = document.getElementById("my-list");
const items = ["项目1", "项目2", "项目3"];
items.forEach((itemText) => {
const li = document.createElement("li");
li.textContent = itemText;
li.addEventListener("click", function() {
console.log(`点击了: ${itemText}`);
});
list.appendChild(li);
});
示例2:表单验证
const form = document.querySelector("#my-form");
const emailInput = document.querySelector("#email");
form.addEventListener("submit", function(event) {
event.preventDefault();
const email = emailInput.value;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert("请输入有效的邮箱地址");
emailInput.focus();
return;
}
// 提交表单
console.log("表单验证通过,可以提交");
// form.submit();
});
示例3:动态加载内容
async function loadUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
// 更新 DOM
document.querySelector("#user-name").textContent = user.name;
document.querySelector("#user-email").textContent = user.email;
document.querySelector("#user-avatar").src = user.avatar;
} catch (error) {
console.error("加载用户数据失败:", error);
}
}
DOM 性能优化建议
1. 缓存 DOM 查询结果
// ❌ 不好:每次都查询 DOM
for (let i = 0; i < 100; i++) {
document.querySelector("#my-element").textContent = i;
}
// ✅ 好:缓存查询结果
const element = document.querySelector("#my-element");
for (let i = 0; i < 100; i++) {
element.textContent = i;
}
2. 使用 DocumentFragment 批量操作
// ✅ 使用 DocumentFragment 减少重排
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.textContent = `项目 ${i}`;
fragment.appendChild(li);
}
document.querySelector("#my-list").appendChild(fragment);
3. 使用事件委托
// ❌ 不好:为每个元素添加事件监听器
document.querySelectorAll(".item").forEach(item => {
item.addEventListener("click", handleClick);
});
// ✅ 好:使用事件委托
document.querySelector("#container").addEventListener("click", function(event) {
if (event.target.classList.contains("item")) {
handleClick(event);
}
});
4. 避免频繁操作样式
// ❌ 不好:每次修改都触发重排
element.style.width = "100px";
element.style.height = "100px";
element.style.backgroundColor = "red";
// ✅ 好:使用类名或一次性修改
element.className = "new-style";
// 或
element.style.cssText = "width: 100px; height: 100px; background-color: red;";
DOM 与现代前端框架
虽然现代前端框架(如 Vue、React、Angular)提供了更高级的抽象层,但理解 DOM 仍然非常重要:
- 框架底层仍然操作 DOM:所有框架最终都要操作 DOM
- 调试和优化:理解 DOM 有助于调试和性能优化
- 原生 JavaScript 开发:在不使用框架的场景下,直接操作 DOM 是必需的
- 学习其他 API:许多 Web API 都基于 DOM 概念
总结
DOM 是前端开发的核心基础,它提供了:
- 文档的树状结构表示:将 HTML 文档转换为可操作的对象树
- 标准化的操作接口:统一的方法来访问和修改文档
- 事件处理机制:响应用户交互的能力
- 跨浏览器兼容性:标准化的 API 确保代码在不同浏览器中工作
掌握 DOM 操作是每个前端开发者的必备技能。虽然现代框架提供了更便捷的开发方式,但深入理解 DOM 原理仍然非常重要,它有助于我们写出更高效、更可靠的代码。