DOM(Document Object Model,文档对象模型)是JavaScript操作网页的接口,而事件处理则是实现交互的核心。本文我将系统梳理DOM的核心操作(元素获取、创建、修改、删除等),深入解析事件处理机制(绑定方式、事件对象、事件委托等),并通过实例演示其应用,帮你`构建完整的DOM交互知识体系。
DOM将HTML文档抽象为树形结构,每个节点(元素、文本、属性等)都是树的组成部分。常见节点类型包括:
、
等标签
- 文本节点(Text):标签内的文本内容
- 属性节点(Attribute):元素的属性(如
class
、id
)
- 文档节点(Document):整个文档的根节点(
document
)
<div id="container">
<p class="text">Hello DOMp>
div>
对应的DOM树结构:
document(文档节点)
└── html(元素节点)
└── body(元素节点)
└── div#container(元素节点)
└── p.text(元素节点)
└── "Hello DOM"(文本节点)
二、DOM元素的获取与访问
操作DOM的第一步是获取目标元素,JavaScript提供了多种获取元素的方法,适用于不同场景。
2.1 基础获取方法
方法
说明
示例
getElementById()
通过id获取唯一元素(大小写敏感)
document.getElementById("container")
getElementsByClassName()
通过class获取元素集合(HTMLCollection)
document.getElementsByClassName("text")
getElementsByTagName()
通过标签名获取元素集合(HTMLCollection)
document.getElementsByTagName("p")
querySelector()
通过CSS选择器获取第一个匹配元素
document.querySelector("#container p")
querySelectorAll()
通过CSS选择器获取所有匹配元素(NodeList)
document.querySelectorAll(".text")
示例代码:
// 获取元素
const container = document.getElementById("container");
const textElements = document.getElementsByClassName("text"); // 集合(类数组)
const pTags = document.getElementsByTagName("p"); // 集合
const firstP = document.querySelector("p"); // 第一个p元素
const allPs = document.querySelectorAll("div p"); // 所有div内的p元素
// 访问集合中的元素(类数组需通过索引访问)
console.log(textElements[0]); // Hello DOM
console.log(allPs[0]); // 第一个匹配的p元素
2.2 集合的区别与注意事项
- HTMLCollection(如
getElementsByClassName()
返回值):实时更新(DOM变化时自动同步),仅包含元素节点;
- NodeList(如
querySelectorAll()
返回值):静态集合(DOM变化时不更新),可能包含文本节点、注释节点等;
- 类数组转换为数组:用
Array.from()
或扩展运算符[...collection]
,方便使用数组方法(如forEach
)。
const htmlColl = document.getElementsByClassName("text");
const nodeList = document.querySelectorAll(".text");
// 转换为数组
const arr1 = Array.from(htmlColl);
const arr2 = [...nodeList];
arr1.forEach(el => console.log(el)); // 遍历元素
三、DOM元素的创建与修改
获取元素后,常需要创建新元素、修改元素属性或内容,实现动态页面效果。
3.1 创建与插入元素
步骤:创建元素 → 设置属性/内容 → 插入到DOM树。
// 1. 创建元素
const newP = document.createElement("p");
// 2. 设置属性和内容
newP.className = "new-text"; // 设置class
newP.id = "new-p"; // 设置id
newP.textContent = "新创建的段落"; // 设置文本内容(推荐,安全)
// 或设置HTML内容(注意XSS风险)
newP.innerHTML = "带标签的内容";
// 3. 插入到DOM树
const container = document.getElementById("container");
container.appendChild(newP); // 插入到container末尾
// 插入到指定元素前
const existingP = container.querySelector("p");
container.insertBefore(newP, existingP); // 插入到existingP前面
3.2 修改元素属性与样式
3.2.1 属性操作
const link = document.querySelector("a");
// 读取属性
console.log(link.href); // 获取href属性值(完整URL)
console.log(link.getAttribute("href")); // 获取原始属性值(如"#top")
// 修改属性
link.href = "https://example.com"; // 修改href
link.setAttribute("title", "示例链接"); // 设置title属性
// 移除属性
link.removeAttribute("class");
3.2.2 样式操作
const box = document.querySelector(".box");
// 方式1:通过style属性修改行内样式(驼峰命名)
box.style.width = "200px";
box.style.backgroundColor = "red"; // 对应CSS的background-color
// 方式2:通过classList操作类名(推荐,分离样式与逻辑)
box.classList.add("active"); // 添加类
box.classList.remove("old"); // 移除类
box.classList.toggle("hidden"); // 切换类(存在则移除,不存在则添加)
box.classList.contains("active"); // 判断是否包含类(返回布尔值)
3.3 元素内容的修改
方法/属性
说明
安全性
textContent
设置或获取元素的文本内容(不含标签)
安全(自动转义HTML)
innerHTML
设置或获取元素的HTML内容(含标签)
不安全(可能导致XSS)
innerText
类似textContent
,但受样式影响(如隐藏文本不显示)
安全
示例:
const p = document.querySelector("p");
p.textContent = "加粗文本"; // 显示为纯文本:加粗文本
p.innerHTML = "加粗文本"; // 显示为加粗文本(解析HTML)
四、DOM元素的删除与替换
移除或替换不需要的元素,可保持DOM结构的整洁。
4.1 删除元素
const toRemove = document.querySelector("#old-element");
// 方式1:通过父元素删除
toRemove.parentNode.removeChild(toRemove);
// 方式2:直接删除(ES6+)
toRemove.remove();
4.2 替换元素
const oldElement = document.querySelector("#old");
const newElement = document.createElement("div");
newElement.textContent = "新元素";
// 用新元素替换旧元素
oldElement.parentNode.replaceChild(newElement, oldElement);
五、事件处理:实现页面交互
事件是用户与页面交互的桥梁(如点击、输入、滚动等),事件处理是实现动态效果的核心。
5.1 事件绑定的三种方式
5.1.1 HTML属性绑定(不推荐)
直接在HTML标签中通过on
前缀属性绑定事件,耦合性高,不利于维护。
<button onclick="handleClick()">点击我button>
<script>
function handleClick() {
alert("按钮被点击");
}
script>
5.1.2 DOM属性绑定
通过元素的事件属性绑定函数,简单直观,但只能绑定一个事件处理函数。
const button = document.querySelector("button");
// 绑定事件
button.onclick = function() {
console.log("按钮被点击");
};
// 解绑事件
button.onclick = null;
5.1.3 addEventListener()
(推荐)
现代事件绑定方式,支持绑定多个处理函数,可指定事件捕获/冒泡阶段,功能最强大。
const button = document.querySelector("button");
// 绑定事件(事件类型,处理函数,是否在捕获阶段触发)
button.addEventListener("click", function() {
console.log("点击事件1");
});
// 绑定第二个处理函数
button.addEventListener("click", handleClick);
function handleClick() {
console.log("点击事件2");
}
// 解绑事件(需使用具名函数)
button.removeEventListener("click", handleClick);
优势:
- 可绑定多个处理函数(按绑定顺序执行);
- 支持事件捕获与冒泡的控制(第三个参数
useCapture
,默认false
为冒泡阶段);
- 可解绑指定的处理函数(需传入与绑定相同的函数引用)。
5.2 事件对象(Event)
事件处理函数被触发时,会自动接收一个事件对象(event
),包含事件相关信息(如触发元素、坐标等)。
document.querySelector("button").addEventListener("click", function(event) {
// 事件对象属性
console.log(event.target); // 触发事件的元素(
console.log(event.currentTarget); // 绑定事件的元素(同target,事件委托时可能不同)
console.log(event.type); // 事件类型("click")
console.log(event.clientX, event.clientY); // 鼠标相对于视口的坐标
console.log(event.preventDefault); // 阻止默认行为的方法
console.log(event.stopPropagation); // 阻止事件冒泡的方法
});
常用方法:
event.preventDefault()
:阻止事件的默认行为(如链接跳转、表单提交);
event.stopPropagation()
:阻止事件冒泡(父元素不再接收该事件);
event.stopImmediatePropagation()
:阻止事件冒泡且阻止当前元素的其他处理函数执行。
示例:阻止链接跳转
document.querySelector("a").addEventListener("click", function(event) {
event.preventDefault(); // 阻止默认跳转行为
console.log("链接被点击,但不跳转");
});
5.3 事件流:捕获与冒泡
事件触发时会经历捕获阶段(从文档根节点到目标元素)和冒泡阶段(从目标元素回到根节点),称为事件流。
<div id="outer">
<div id="inner">点击我div>
div>
// 捕获阶段触发(useCapture: true)
outer.addEventListener("click", () => console.log("outer捕获"), true);
inner.addEventListener("click", () => console.log("inner捕获"), true);
// 冒泡阶段触发(useCapture: false,默认)
outer.addEventListener("click", () => console.log("outer冒泡"), false);
inner.addEventListener("click", () => console.log("inner冒泡"), false);
点击inner
元素时,输出顺序:
outer捕获 → inner捕获 → inner冒泡 → outer冒泡
应用:利用冒泡机制实现事件委托(见5.4节)。
5.4 事件委托:高效处理动态元素
事件委托利用事件冒泡,将子元素的事件委托给父元素处理,适用于动态生成的元素(无需重复绑定事件)。
场景:列表项动态添加,点击列表项需要触发事件。
<ul id="list">
<li>项目1li>
<li>项目2li>
ul>
<button id="addItem">添加项目button>
const list = document.getElementById("list");
// 事件委托:将li的点击事件委托给ul
list.addEventListener("click", function(event) {
// 判断触发事件的是否为li元素
if (event.target.tagName === "LI") {
console.log("点击了:", event.target.textContent);
}
});
// 动态添加li元素(无需重新绑定事件)
document.getElementById("addItem").addEventListener("click", function() {
const newLi = document.createElement("li");
newLi.textContent = `项目${list.children.length + 1}`;
list.appendChild(newLi);
});
优势:
- 减少事件绑定次数,提升性能(尤其对于大量子元素);
- 自动支持动态添加的元素,无需额外绑定;
- 简化代码,便于维护。
六、常见问题与避坑指南
6.1 DOM操作的性能问题
频繁操作DOM会导致浏览器频繁重绘(Repaint)和重排(Reflow),影响性能。
解决方案:
- 批量操作:通过文档片段(
DocumentFragment
)一次性插入多个元素;
- 临时隐藏:操作前隐藏元素(
display: none
),操作后显示;
- 避免频繁查询:将获取的元素存储在变量中,避免重复查询DOM。
// 优化前:频繁插入元素(多次重排)
const list = document.getElementById("list");
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.textContent = `项目${i}`;
list.appendChild(li); // 每次循环都触发重排
}
// 优化后:用DocumentFragment批量插入
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.textContent = `项目${i}`;
fragment.appendChild(li); // 先添加到文档片段(不触发重排)
}
list.appendChild(fragment); // 一次性插入(仅一次重排)
6.2 事件解绑失败
使用addEventListener()
绑定的匿名函数无法解绑,因解绑需要相同的函数引用。
const btn = document.querySelector("button");
// 错误:匿名函数无法解绑
btn.addEventListener("click", () => console.log("点击"));
btn.removeEventListener("click", () => console.log("点击")); // 无效
// 正确:使用具名函数
function handleClick() {
console.log("点击");
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick); // 有效
6.3 innerHTML
的安全风险
innerHTML
会解析HTML标签,若内容包含用户输入,可能导致XSS攻击(跨站脚本)。
const userInput = '
';
// 危险:直接插入用户输入
element.innerHTML = userInput; // 会执行onerror中的代码
// 安全:使用textContent(自动转义HTML)
element.textContent = userInput; // 显示为纯文本,无安全风险
总结:DOM操作与事件处理的核心要点:
- DOM操作:
- 元素获取:优先使用
querySelector()
/querySelectorAll()
,灵活支持CSS选择器;
- 元素修改:
textContent
安全修改文本,classList
操作类名更便捷;
- 性能优化:批量操作DOM时使用
DocumentFragment
,减少重排重绘。
- 事件处理:
- 绑定方式:优先使用
addEventListener()
,支持多处理函数和事件流控制;
- 事件对象:利用
event.target
获取触发元素,preventDefault()
阻止默认行为;
- 事件委托:通过父元素代理子元素事件,高效处理动态元素。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ