当前位置: 首页 > news >正文

DOM编程全解析:操作、事件与存储实战指南

引言:DOM——JavaScript与网页交互的桥梁

DOM(文档对象模型) 是JavaScript操作HTML/XML文档的接口,它将网页文档抽象为一个树形结构,允许开发者通过API动态修改文档的内容、结构和样式。无论是实现动态交互(如按钮点击)、表单验证,还是数据持久化(如本地存储用户偏好),DOM编程都是前端开发的核心基础。

本文将系统讲解DOM编程的三大核心模块:DOM操作(元素创建、修改、删除)、事件系统(事件绑定、事件流、事件对象)、表单与存储(表单处理、验证、Web Storage),每个知识点均配备可直接运行的示例代码,帮助你从入门到精通DOM编程。

一、DOM基础:理解文档树与节点类型

1.1 DOM树结构与节点类型

DOM将HTML文档表示为一棵节点树,每个HTML元素、文本、属性都是树中的节点。常见节点类型包括:

节点类型描述示例
元素节点HTML标签元素<div><p><input>
文本节点元素内的文本内容Hello World<p>内的文本)
属性节点元素的属性class="active"id="logo"
文档节点整个文档的根节点document对象
注释节点HTML注释<!-- 这是注释 -->

示例:简单DOM树结构

<html><head><title>DOM示例</title></head><body><h1>欢迎学习DOM</h1><p class="content">这是一个段落</p></body>
</html>

对应的DOM树:

document(文档节点)
└── html(元素节点)├── head(元素节点)│   └── title(元素节点)│       └── "DOM示例"(文本节点)└── body(元素节点)├── h1(元素节点)│   └── "欢迎学习DOM"(文本节点)└── p(元素节点,属性:class="content")└── "这是一个段落"(文本节点)

1.2 获取DOM元素:从选择到访问

操作DOM的第一步是获取元素,JavaScript提供了多种选择器API,适用于不同场景:

1.2.1 基础选择器(直接获取单个/多个元素)
// 1. 通过ID获取(返回单个元素,推荐,性能最优)
const title = document.getElementById("main-title");// 2. 通过类名获取(返回HTMLCollection,类数组,实时更新)
const items = document.getElementsByClassName("list-item");// 3. 通过标签名获取(返回HTMLCollection)
const paragraphs = document.getElementsByTagName("p");// 4. 通过name属性获取(返回NodeList,常用于表单元素)
const usernameInput = document.getElementsByName("username");
1.2.2 高级选择器(CSS选择器语法,灵活强大)
// 1. querySelector(返回第一个匹配元素,支持复杂CSS选择器)
const firstItem = document.querySelector(".list-item:first-child");
const activeLink = document.querySelector("a.active");// 2. querySelectorAll(返回NodeList,非实时更新,支持forEach遍历)
const allItems = document.querySelectorAll("ul li");
const inputs = document.querySelectorAll('input[type="text"]');// NodeList遍历示例
allItems.forEach((item, index) => {console.log(`${index+1}个列表项:`, item.textContent);
});

注意HTMLCollection(如getElementsByClassName返回)是实时集合(DOM变化时自动更新),而NodeList(如querySelectorAll返回)默认非实时,但支持forEach遍历,更推荐使用。

二、DOM操作:动态修改文档结构与样式

2.1 节点CRUD:创建、插入、删除与替换

2.1.1 创建节点
// 创建元素节点
const newDiv = document.createElement("div");// 创建文本节点
const textNode = document.createTextNode("这是动态创建的文本");// 创建属性节点(较少直接使用,通常通过setAttribute设置)
const classAttr = document.createAttribute("class");
classAttr.value = "dynamic-div";
newDiv.setAttributeNode(classAttr); // 等价于 newDiv.className = "dynamic-div"// 组合节点
newDiv.appendChild(textNode); // 将文本节点添加到div中
newDiv.innerHTML = "<p>通过innerHTML直接设置HTML内容</p>"; // 更简洁的方式
2.1.2 插入节点
const container = document.getElementById("container");
const newP = document.createElement("p");
newP.textContent = "新段落";// 1. appendChild:添加到容器末尾
container.appendChild(newP);// 2. insertBefore:插入到指定节点之前(需指定参考节点)
const referenceNode = container.querySelector("p:first-child");
container.insertBefore(newP, referenceNode); // 插入到第一个p元素之前// 3. 现代API:prepend(添加到开头)、append(添加到末尾,支持多个节点)
container.prepend(newP, document.createElement("hr")); // 开头添加p和hr
2.1.3 删除与替换节点
const oldNode = document.getElementById("old-node");
const newNode = document.createElement("span");
newNode.textContent = "替换后的内容";// 删除节点(需通过父节点调用)
oldNode.parentNode.removeChild(oldNode);// 替换节点
oldNode.parentNode.replaceChild(newNode, oldNode);

2.2 修改元素属性与样式

2.2.1 属性操作
const link = document.querySelector("a");// 获取属性
console.log(link.getAttribute("href")); // 获取href值
console.log(link.href); // 直接访问属性(返回绝对路径,与getAttribute不同)// 设置属性
link.setAttribute("href", "https://example.com");
link.title = "示例链接"; // 直接赋值(更简洁)// 移除属性
link.removeAttribute("target");// 检查属性是否存在
console.log(link.hasAttribute("rel")); // true/false
2.2.2 样式操作
const box = document.querySelector(".box");// 1. 内联样式(style属性,优先级最高)
box.style.width = "200px"; // 注意:属性名是驼峰式(如fontSize)
box.style.backgroundColor = "#f0f0f0"; // 等价于CSS的background-color
box.style.cssText = "width: 300px; height: 200px; border: 1px solid red;"; // 批量设置// 2. 类名操作(推荐,通过CSS类控制样式,便于维护)
box.classList.add("active"); // 添加类
box.classList.remove("old-class"); // 移除类
box.classList.toggle("hidden"); // 切换类(存在则移除,不存在则添加)
box.classList.contains("active"); // 检查类是否存在(返回true/false)// 3. 获取计算样式(最终应用的样式,包含所有CSS来源)
const computedStyle = window.getComputedStyle(box);
console.log("计算后的宽度:", computedStyle.width);
console.log("字体大小:", computedStyle.fontSize);

2.3 批量操作优化:DocumentFragment

频繁DOM操作会导致重排重绘(Reflow/Repaint),影响性能。使用DocumentFragment可以批量处理节点,减少DOM操作次数:

const list = document.getElementById("big-list");
const fragment = document.createDocumentFragment(); // 文档片段,内存中临时容器// 模拟批量创建100个列表项
for (let i = 0; i < 100; i++) {const li = document.createElement("li");li.textContent = `列表项 ${i+1}`;fragment.appendChild(li); // 先添加到fragment(不触发重排)
}// 最后一次性添加到DOM(仅触发一次重排)
list.appendChild(fragment);

三、事件系统:响应用户交互的核心机制

3.1 事件流:捕获→目标→冒泡

DOM事件流分为三个阶段(IE8及以下仅支持冒泡阶段):

  1. 捕获阶段:事件从window向下传播到目标元素的父节点
  2. 目标阶段:事件到达目标元素本身
  3. 冒泡阶段:事件从目标元素向上传播回window

示例:事件流演示

<div id="outer" style="padding: 20px; background: #f0f0f0;"><div id="inner" style="padding: 20px; background: #ccc;"><button id="btn">点击我</button></div>
</div>
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
const btn = document.getElementById("btn");// 捕获阶段绑定(第三个参数为true)
outer.addEventListener("click", () => console.log("outer捕获"), true);
inner.addEventListener("click", () => console.log("inner捕获"), true);
btn.addEventListener("click", () => console.log("btn捕获"), true);// 冒泡阶段绑定(第三个参数为false或省略)
outer.addEventListener("click", () => console.log("outer冒泡"));
inner.addEventListener("click", () => console.log("inner冒泡"));
btn.addEventListener("click", () => console.log("btn冒泡"));

点击按钮后输出顺序

outer捕获 → inner捕获 → btn捕获 → btn冒泡 → inner冒泡 → outer冒泡

3.2 事件绑定方式对比

3.2.1 内联事件(HTML属性,不推荐)
<!-- 直接在HTML中绑定,耦合度高,难以维护 -->
<button onclick="alert('点击了!')">按钮1</button>
<button onclick="handleClick()">按钮2</button><script>function handleClick() {alert("通过函数绑定点击");}
</script>
3.2.2 DOM属性绑定(JavaScript,单事件绑定)
const btn = document.getElementById("btn");// 覆盖式绑定(只能绑定一个事件处理函数)
btn.onclick = function() {console.log("点击事件1");
};
btn.onclick = function() {console.log("点击事件2"); // 覆盖前一个事件处理函数
};// 移除事件
btn.onclick = null;
3.2.3 addEventListener(推荐,支持多事件绑定与事件流控制)
const btn = document.getElementById("btn");// 绑定多个事件处理函数
btn.addEventListener("click", function handler1() {console.log("点击事件1");
});
btn.addEventListener("click", function handler2() {console.log("点击事件2"); // 两个事件都会执行
});// 移除事件(必须使用命名函数,匿名函数无法移除)
btn.removeEventListener("click", handler1);// 绑定捕获阶段事件
btn.addEventListener("click", () => console.log("捕获阶段事件"), true);

推荐使用addEventListener:支持绑定多个事件、控制事件流阶段、灵活移除事件,是现代DOM事件绑定的标准方式。

3.3 事件委托:高效处理动态元素事件

事件委托利用事件冒泡机制,将子元素的事件统一绑定到父元素上,尤其适合动态生成的元素(如列表项、动态加载的内容)。

3.3.1 传统方式的问题(动态元素无法绑定事件)
// 初始列表项可以绑定事件,但动态添加的无法触发
document.querySelectorAll("ul li").forEach(li => {li.addEventListener("click", () => {console.log("点击了列表项:", li.textContent);});
});// 动态添加新列表项(无法触发点击事件)
const newLi = document.createElement("li");
newLi.textContent = "新列表项";
document.querySelector("ul").appendChild(newLi);
3.3.2 事件委托解决方案
const ul = document.querySelector("ul");// 将事件绑定到父元素ul上
ul.addEventListener("click", (e) => {// 通过事件对象的target判断是否点击了liif (e.target.tagName === "LI") { // 注意tagName是大写console.log("点击了列表项:", e.target.textContent);// 可以通过closest进一步精确匹配(如带特定类的li)const targetLi = e.target.closest("li.active");if (targetLi) {console.log("点击了激活状态的列表项");}}
});// 动态添加的li也能触发事件
const newLi = document.createElement("li");
newLi.textContent = "新列表项(支持点击)";
ul.appendChild(newLi);

优势:减少事件绑定次数(只绑定父元素)、自动支持动态元素、优化性能(尤其对大量子元素)。

3.4 事件对象:获取事件详情与控制行为

事件处理函数的第一个参数为事件对象(Event),包含事件相关信息(如触发元素、坐标、按键等),并提供控制事件行为的方法。

3.4.1 常用事件对象属性
document.addEventListener("click", (e) => {console.log("触发元素:", e.target); // 实际点击的元素(可能是子元素)console.log("绑定事件的元素:", e.currentTarget); // 绑定事件的父元素(此处为document)console.log("事件类型:", e.type); // "click"console.log("鼠标坐标(相对于页面):", e.pageX, e.pageY);console.log("是否按下Ctrl键:", e.ctrlKey);
});
3.4.2 常用事件对象方法
// 1. preventDefault:阻止默认行为(如链接跳转、表单提交)
document.querySelector("a").addEventListener("click", (e) => {e.preventDefault(); // 阻止链接跳转console.log("链接被点击,但已阻止跳转");
});// 2. stopPropagation:阻止事件冒泡(不再向上传播)
document.getElementById("inner").addEventListener("click", (e) => {e.stopPropagation(); // 阻止冒泡到outerconsole.log("inner点击,已阻止冒泡");
});// 3. stopImmediatePropagation:阻止冒泡+阻止同元素后续事件处理函数
btn.addEventListener("click", (e) => {e.stopImmediatePropagation();console.log("第一个点击事件");
});
btn.addEventListener("click", () => {console.log("第二个点击事件(不会执行)");
});

四、表单处理:获取值、验证与提交

4.1 表单元素值获取

4.1.1 基础表单元素
// 获取表单元素
const form = document.getElementById("user-form");
const username = form.elements.username; // 通过form.elements获取(推荐)
const password = document.querySelector('input[name="password"]');
const email = document.getElementById("email");// 获取值
console.log("用户名:", username.value);
console.log("密码:", password.value);
console.log("邮箱:", email.value);
4.1.2 特殊表单元素(单选、多选、下拉框)
// 单选按钮(获取选中的值)
const genderRadios = document.querySelectorAll('input[name="gender"]');
const selectedGender = Array.from(genderRadios).find(radio => radio.checked)?.value;// 复选框(获取所有选中的值)
const hobbiesCheckboxes = document.querySelectorAll('input[name="hobbies"]:checked');
const selectedHobbies = Array.from(hobbiesCheckboxes).map(checkbox => checkbox.value);// 下拉框(select元素)
const citySelect = document.getElementById("city");
const selectedCity = citySelect.value; // 单选下拉框
const selectedCities = Array.from(citySelect.selectedOptions).map(option => option.value); // 多选下拉框(需设置multiple)

4.2 表单验证

4.2.1 HTML5原生验证(简单高效)
<form id="login-form"><div><label>用户名:</label><input type="text" name="username" required  <!-- 必填 -->minlength="3"  <!-- 最小长度 -->maxlength="10"  <!-- 最大长度 -->pattern="^[a-zA-Z0-9_]+$"  <!-- 正则匹配(字母、数字、下划线) -->title="用户名只能包含字母、数字和下划线,长度3-10"  <!-- 验证失败提示 -->></div><div><label>邮箱:</label><input type="email" name="email" required> <!-- 自动验证邮箱格式 --></div><div><label>年龄:</label><input type="number" name="age" min="18" max="120" required> <!-- 数字范围 --></div><button type="submit">提交</button>
</form>
4.2.2 自定义验证(复杂逻辑)
const form = document.getElementById("login-form");// 自定义验证函数
function validateUsername(username) {if (username.includes("admin")) {return { valid: false, message: "用户名不能包含'admin'" };}return { valid: true };
}// 监听表单提交事件
form.addEventListener("submit", (e) => {e.preventDefault(); // 阻止默认提交const username = form.username.value;const email = form.email.value;// 执行自定义验证const usernameValid = validateUsername(username);if (!usernameValid.valid) {alert(usernameValid.message);return;}// 原生验证API(checkValidity检查单个元素,reportValidity显示默认提示)if (!form.email.checkValidity()) {form.email.reportValidity(); // 触发浏览器默认错误提示return;}// 验证通过,提交表单(AJAX示例)fetch("/api/login", {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify({ username, email })}).then(response => response.json()).then(data => console.log("登录成功:", data)).catch(error => console.error("登录失败:", error));
});

五、Web存储:持久化数据存储方案

5.1 localStorage与sessionStorage对比

特性localStoragesessionStorage
存储周期永久存储,除非手动删除仅当前会话(标签页关闭后清除)
作用域同源窗口共享仅当前标签页私有
存储容量约5MB约5MB
APIsetItem/getItem/removeItem/clear同上

5.2 基础API使用

5.2.1 localStorage示例(持久化存储)
// 存储数据(键值对,值只能是字符串)
localStorage.setItem("username", "Alice");
localStorage.setItem("isLogin", "true"); // 布尔值会被转为字符串// 获取数据
const username = localStorage.getItem("username");
const isLogin = localStorage.getItem("isLogin") === "true"; // 需手动转换类型// 删除数据
localStorage.removeItem("isLogin");// 清空所有数据(谨慎使用)
// localStorage.clear();
5.2.2 存储复杂数据(JSON序列化)
// 存储对象
const userInfo = { name: "Bob", age: 25, hobbies: ["reading", "coding"] };
localStorage.setItem("userInfo", JSON.stringify(userInfo)); // 序列化为JSON字符串// 获取对象
const storedUser = JSON.parse(localStorage.getItem("userInfo")); // 反序列化为对象
console.log("用户名:", storedUser.name);
console.log("年龄:", storedUser.age);// 存储数组
const recentSearches = ["JavaScript", "DOM编程"];
localStorage.setItem("recentSearches", JSON.stringify(recentSearches));
5.2.3 sessionStorage示例(会话存储)
// 仅在当前标签页有效,刷新页面保留,关闭标签页丢失
sessionStorage.setItem("tempData", "这是临时数据");// 跨标签页无法访问
// 在新标签页执行 sessionStorage.getItem("tempData") → null

5.3 存储限制与安全考量

  • 容量限制:单个域名下约5MB,超出会抛出QuotaExceededError
  • 安全风险:存储内容明文可见,不要存储敏感信息(如密码、token)。
  • 存储优化:定期清理过期数据,复杂数据使用IndexedDB(更大容量,支持查询)。

六、综合实战:待办事项列表(DOM+事件+存储)

6.1 需求与HTML结构

实现一个待办事项列表,支持添加、删除、标记完成功能,并使用localStorage持久化数据。

<div class="todo-app"><h1>待办事项列表</h1><form id="todo-form"><input type="text" id="todo-input" placeholder="输入新任务..." required><button type="submit">添加</button></form><ul id="todo-list"></ul>
</div>

6.2 JavaScript实现

class TodoApp {constructor() {this.todoList = document.getElementById("todo-list");this.todoInput = document.getElementById("todo-input");this.form = document.getElementById("todo-form");this.todos = JSON.parse(localStorage.getItem("todos")) || []; // 从存储加载数据this.init();}// 初始化:绑定事件、渲染列表init() {this.form.addEventListener("submit", (e) => this.handleAddTodo(e));this.todoList.addEventListener("click", (e) => this.handleTodoClick(e)); // 事件委托this.renderTodos();}// 渲染待办列表renderTodos() {this.todoList.innerHTML = ""; // 清空列表this.todos.forEach((todo, index) => {const li = document.createElement("li");li.className = todo.completed ? "completed" : "";li.innerHTML = `<input type="checkbox" ${todo.completed ? "checked" : ""}><span>${todo.text}</span><button class="delete-btn">×</button>`;li.dataset.index = index; // 存储索引,用于删除this.todoList.appendChild(li);});// 保存到localStoragelocalStorage.setItem("todos", JSON.stringify(this.todos));}// 添加待办事项handleAddTodo(e) {e.preventDefault();const text = this.todoInput.value.trim();if (!text) return;this.todos.push({ text, completed: false });this.todoInput.value = ""; // 清空输入框this.renderTodos();}// 处理待办项点击(标记完成/删除)handleTodoClick(e) {const index = e.target.closest("li").dataset.index;if (e.target.type === "checkbox") {// 标记完成/未完成this.todos[index].completed = e.target.checked;} else if (e.target.classList.contains("delete-btn")) {// 删除待办项this.todos.splice(index, 1);}this.renderTodos();}
}// 初始化应用
new TodoApp();

6.3 样式补充(CSS)

.todo-app { max-width: 500px; margin: 20px auto; padding: 0 20px; }
#todo-list { list-style: none; padding: 0; }
#todo-list li { padding: 10px; margin: 5px 0; background: #f5f5f5; border-radius: 4px;display: flex; align-items: center; gap: 10px;
}
#todo-list li.completed span { text-decoration: line-through; color: #888; }
.delete-btn { margin-left: auto; background: #ff4444; color: white; border: none; border-radius: 50%; cursor: pointer; }

七、常见问题与最佳实践

7.1 DOM操作性能优化

  • 减少重排重绘:批量修改DOM时使用DocumentFragment,避免频繁修改style属性(使用classList替代)。
  • 事件委托:对动态元素使用事件委托,减少事件绑定次数。
  • 缓存DOM查询:避免在循环中重复查询DOM(如for (let i=0; i<document.querySelectorAll('li').length; i++))。

7.2 事件绑定注意事项

  • 移除事件监听:使用addEventListener绑定的事件,需通过removeEventListener移除(传入相同函数),避免内存泄漏。
  • 阻止默认行为:表单提交、链接跳转需显式调用e.preventDefault()

7.3 存储安全与限制

  • 敏感数据:禁止存储密码、token等,可使用HttpOnly Cookie或加密存储。
  • 容量控制:定期清理无用数据,超出5MB时提供用户提示。

八、总结与进阶学习

DOM编程是前端开发的基石,本文涵盖了DOM操作、事件系统、表单处理与Web存储的核心知识点,并通过实战案例展示了如何综合应用。掌握这些技能后,你可以实现复杂的交互效果和数据持久化功能。

进阶学习方向

  • DOM高级APIIntersectionObserver(滚动监听)、MutationObserver(DOM变化监听)
  • 虚拟DOM:理解React/Vue的虚拟DOM原理,减少真实DOM操作
  • Web Components:自定义元素,封装可复用的DOM组件
  • IndexedDB:替代localStorage的大容量结构化存储方案

通过持续实践(如实现购物车、表单生成器),你将逐步提升DOM编程能力,为构建复杂前端应用打下坚实基础!#### 4.1.2 复杂表单元素(单选框、复选框、下拉框)

// 单选框(name属性相同的为一组)
const genderRadios = document.querySelectorAll('input[name="gender"]');
let selectedGender;
genderRadios.forEach(radio => {if (radio.checked) {selectedGender = radio.value; // 获取选中值}
});// 复选框(获取所有选中项)
const hobbies = Array.from(document.querySelectorAll('input[name="hobby"]:checked')).map(checkbox => checkbox.value); // 转换为值数组// 下拉框(select)
const citySelect = document.getElementById("city");
const selectedCity = citySelect.value; // 单选下拉框
const selectedOptions = Array.from(citySelect.selectedOptions).map(option => option.value); // 多选下拉框(需设置multiple属性)// 文件上传(input[type="file"])
const fileInput = document.getElementById("avatar");
fileInput.addEventListener("change", (e) => {const file = e.target.files[0]; // 获取第一个选中文件console.log("文件名:", file.name);console.log("文件大小:", file.size);console.log("文件类型:", file.type);
});

4.2 表单验证:HTML5验证与自定义验证

4.2.1 HTML5内置验证属性
<form id="register-form"><!-- 必填项 --><input type="text" name="username" required placeholder="用户名(必填)"><!-- 最小/最大长度 --><input type="password" name="password" minlength="6" maxlength="20" required><!-- 正则表达式验证 --><input type="text" name="phone" pattern="^1[3-9]\d{9}$" placeholder="手机号"><!-- 数值范围 --><input type="number" name="age" min="18" max="120" placeholder="年龄(18-120)"><!-- 邮箱格式验证 --><input type="email" name="email" required placeholder="邮箱"><button type="submit">提交</button>
</form>
4.2.2 JavaScript触发验证
const form = document.getElementById("register-form");form.addEventListener("submit", (e) => {// 触发表单验证(若验证不通过,会显示默认错误提示)if (!form.checkValidity()) {e.preventDefault(); // 阻止表单提交console.log("表单验证失败");// 手动聚焦到第一个无效字段const invalidField = form.querySelector(":invalid");invalidField.focus();} else {e.preventDefault(); // 实际项目中通过AJAX提交console.log("表单验证通过,准备提交");}
});
4.2.3 自定义验证逻辑与错误提示
const password = document.getElementById("password");
const confirmPassword = document.getElementById("confirm-password");// 自定义验证:密码一致性检查
confirmPassword.addEventListener("input", () => {if (password.value !== confirmPassword.value) {// 设置自定义错误消息confirmPassword.setCustomValidity("两次密码不一致");} else {confirmPassword.setCustomValidity(""); // 清除错误}
});// 自定义错误提示样式(覆盖浏览器默认提示)
form.addEventListener("invalid", (e) => {e.preventDefault(); // 阻止默认提示框const field = e.target;const errorMsg = field.validationMessage || "请输入有效值";// 显示自定义错误提示(假设页面有.error-message元素)const errorElement = field.nextElementSibling;if (errorElement && errorElement.classList.contains("error-message")) {errorElement.textContent = errorMsg;}
}, true); // 捕获阶段触发

五、Web存储:localStorage、sessionStorage与Cookie

5.1 localStorage:持久化本地存储

特点:永久存储(除非手动删除)、容量约5MB、同源策略限制、仅支持字符串存储。

5.1.1 基本API
// 存储数据(自动转为字符串)
localStorage.setItem("username", "Alice");
localStorage.setItem("userAge", 25); // 存储为"25"
localStorage.setItem("isVip", true); // 存储为"true"// 获取数据(需手动转换类型)
const username = localStorage.getItem("username"); // "Alice"
const userAge = Number(localStorage.getItem("userAge")); // 25
const isVip = localStorage.getItem("isVip") === "true"; // true// 存储对象(需JSON序列化)
const user = { name: "Bob", hobbies: ["reading", "coding"] };
localStorage.setItem("user", JSON.stringify(user));// 获取对象(需JSON解析)
const savedUser = JSON.parse(localStorage.getItem("user"));
console.log(savedUser.hobbies[0]); // "reading"// 删除数据
localStorage.removeItem("userAge");// 清空所有数据
// localStorage.clear();
5.1.2 监听存储变化(跨标签页通信)
window.addEventListener("storage", (e) => {console.log(`存储键${e.key}发生变化`);console.log("旧值:", e.oldValue);console.log("新值:", e.newValue);console.log("存储区域:", e.storageArea); // localStorage或sessionStorage
});

5.2 sessionStorage:会话级存储

特点:仅在当前会话有效(标签页关闭后清除)、容量约5MB、同源策略限制、API与localStorage完全相同。

5.2.1 使用场景:临时表单数据保存
// 监听表单输入,实时保存到sessionStorage
document.querySelectorAll("#temp-form input").forEach(input => {input.addEventListener("input", (e) => {const { name, value } = e.target;sessionStorage.setItem(`temp_${name}`, value);});
});// 页面加载时恢复表单数据(如刷新页面后)
window.addEventListener("load", () => {document.querySelectorAll("#temp-form input").forEach(input => {const savedValue = sessionStorage.getItem(`temp_${input.name}`);if (savedValue) {input.value = savedValue;}});
});

5.3 Cookie:小型文本存储

特点:容量小(约4KB)、可设置有效期、可指定域名和路径、每次请求自动携带。

5.3.1 Cookie操作函数
// 设置Cookie(name, value, daysToLive:有效期天数)
function setCookie(name, value, daysToLive = 7, path = "/", domain = "", secure = false) {let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;// 有效期if (daysToLive) {const date = new Date();date.setTime(date.getTime() + (daysToLive * 24 * 60 * 60 * 1000));cookie += `; expires=${date.toUTCString()}`;}// 路径if (path) cookie += `; path=${path}`;// 域名if (domain) cookie += `; domain=${domain}`;// 安全标志(仅HTTPS)if (secure) cookie += `; secure`;document.cookie = cookie;
}// 获取Cookie
function getCookie(name) {const cookies = document.cookie.split("; ");for (const cookie of cookies) {const [cookieName, cookieValue] = cookie.split("=");if (decodeURIComponent(cookieName) === name) {return decodeURIComponent(cookieValue);}}return null;
}// 删除Cookie(设置过期时间为过去)
function deleteCookie(name, path = "/", domain = "") {setCookie(name, "", -1, path, domain);
}// 使用示例
setCookie("theme", "dark", 30); // 存储30天
const theme = getCookie("theme"); // "dark"
deleteCookie("theme");
5.3.2 Cookie与Storage对比
特性localStoragesessionStorageCookie
存储容量约5MB约5MB约4KB
有效期永久(手动删除)会话期间可设置(默认会话)
网络请求不发送到服务器不发送到服务器每次请求自动携带
访问权限客户端脚本客户端脚本客户端/服务器端
适用场景用户偏好、持久数据临时表单、页面状态身份认证、跟踪

六、综合实战案例:待办事项管理系统

6.1 功能需求

  • 添加待办事项(支持回车提交)
  • 标记待办事项为已完成/未完成
  • 删除待办事项
  • 数据持久化(localStorage保存)
  • 实时统计待办/已完成数量

6.2 实现代码

HTML结构
<div class="todo-app"><h1>待办事项管理</h1><div class="stats"><span>待办:<span id="todo-count">0</span></span><span>已完成:<span id="done-count">0</span></span></div><form id="todo-form"><input type="text" id="todo-input" placeholder="添加新的待办事项..."><button type="submit">添加</button></form><ul id="todo-list" class="todo-list"></ul>
</div>
CSS样式(简化版)
.todo-app { max-width: 500px; margin: 20px auto; padding: 0 20px; }
.todo-list { list-style: none; padding: 0; }
.todo-item { padding: 10px; margin: 5px 0; border: 1px solid #ddd; display: flex; align-items: center; }
.todo-item.done .text { text-decoration: line-through; color: #999; }
.todo-item input[type="checkbox"] { margin-right: 10px; }
.todo-item .delete { margin-left: auto; cursor: pointer; color: red; }
.stats { margin-bottom: 10px; color: #666; }
JavaScript逻辑
document.addEventListener("DOMContentLoaded", () => {const form = document.getElementById("todo-form");const input = document.getElementById("todo-input");const todoList = document.getElementById("todo-list");const todoCountEl = document.getElementById("todo-count");const doneCountEl = document.getElementById("done-count");// 从localStorage加载待办事项let todos = JSON.parse(localStorage.getItem("todos")) || [];// 渲染待办事项列表function renderTodos() {todoList.innerHTML = ""; // 清空列表todos.forEach((todo, index) => {const li = document.createElement("li");li.className = `todo-item ${todo.done ? "done" : ""}`;li.innerHTML = `<input type="checkbox" ${todo.done ? "checked" : ""} data-index="${index}"><span class="text">${todo.text}</span><span class="delete" data-index="${index}">×</span>`;todoList.appendChild(li);});// 更新统计const todoCount = todos.filter(todo => !todo.done).length;const doneCount = todos.length - todoCount;todoCountEl.textContent = todoCount;doneCountEl.textContent = doneCount;// 保存到localStoragelocalStorage.setItem("todos", JSON.stringify(todos));}// 添加待办事项form.addEventListener("submit", (e) => {e.preventDefault();const text = input.value.trim();if (text) {todos.push({ text, done: false });input.value = ""; // 清空输入框renderTodos();}});// 回车提交input.addEventListener("keydown", (e) => {if (e.key === "Enter") form.dispatchEvent(new Event("submit"));});// 事件委托:处理复选框和删除按钮点击todoList.addEventListener("click", (e) => {const index = e.target.dataset.index;if (index === undefined) return;if (e.target.type === "checkbox") {// 切换完成状态todos[index].done = e.target.checked;} else if (e.target.classList.contains("delete")) {// 删除待办事项todos.splice(index, 1);}renderTodos();});// 初始化渲染renderTodos();
});

七、DOM编程性能优化与最佳实践

7.1 DOM操作性能优化

7.1.1 减少重排重绘
  • 批量修改DOM:使用DocumentFragment或离线DOM树
  • 避免频繁读取布局属性:如offsetWidth/offsetHeight(会触发重排),缓存计算结果
  • 使用CSS类批量修改样式:避免多次设置element.style
  • 隐藏元素修改后再显示element.style.display = "none"; 修改...; element.style.display = "block";
7.1.2 事件优化
  • 事件委托:减少事件绑定数量
  • 移除无用事件监听:页面卸载或元素删除前解绑事件,避免内存泄漏
  • 防抖节流:处理高频事件(resize、scroll、input)
// 防抖(一段时间内只执行最后一次)
function debounce(fn, delay = 300) {let timer;return (...args) => {clearTimeout(timer);timer = setTimeout(() => fn.apply(this, args), delay);};
}// 节流(一段时间内只执行一次)
function throttle(fn, interval = 300) {let lastTime = 0;return (...args) => {const now = Date.now();if (now - lastTime >= interval) {fn.apply(this, args);lastTime = now;}};
}// 使用示例(监听窗口大小变化)
window.addEventListener("resize", debounce(() => {console.log("窗口大小变化(防抖)");
}, 500));

7.2 安全最佳实践

  • 避免innerHTML插入不可信内容:防止XSS攻击,优先使用textContent
  • 表单验证:前后端双重验证,不信任前端输入
  • 存储敏感数据:避免在localStorage/Cookie存储密码等敏感信息,必要时加密

八、总结与进阶学习

8.1 核心知识点回顾

  • DOM操作:元素选择、内容修改、样式操作、节点CRUD
  • 事件系统:事件流、委托、事件对象方法、高频事件优化
  • 表单处理:值获取、HTML5验证、自定义验证逻辑
  • Web存储:localStorage/sessionStorage/Cookie的使用场景与限制

8.2 进阶学习方向

  • DOM高级API:MutationObserver(监听DOM变化)、IntersectionObserver(懒加载)
  • 虚拟DOM:理解React/Vue的虚拟DOM原理
  • Web Components:自定义元素、Shadow DOM
  • 性能监控:使用Chrome DevTools分析DOM性能瓶颈

8.3 推荐资源

  • 官方文档:MDN DOM文档

DOM编程是前端开发的基石,掌握本文内容后,你将能够自如地实现网页动态交互、表单处理和数据持久化。实践是提升的关键,建议结合本文案例动手实现,并尝试扩展功能(如待办事项的分类、优先级设置),深化对DOM编程的理解。

补充:存储方案详细对比表格

特性localStoragesessionStorageCookie
存储容量约5MB约5MB约4KB
有效期永久(需手动删除)会话期间(标签页关闭后清除)可设置(默认会话)
网络请求不发送到服务器不发送到服务器每次HTTP请求自动携带
访问权限仅客户端JavaScript仅客户端JavaScript客户端/服务器端均可访问
API易用性简洁(setItem/getItem等)同localStorage需手动解析document.cookie
数据类型仅字符串(需JSON序列化)同localStorage仅字符串
跨标签页共享支持(同源)不支持支持(符合路径/域限制)
适用场景用户偏好、持久化数据临时表单数据、页面状态身份认证、会话跟踪

补充:待办事项系统编辑功能扩展

在原案例基础上添加双击编辑功能:

// 事件委托:添加双击编辑功能
todoList.addEventListener("dblclick", (e) => {const index = e.target.closest(".todo-item").dataset.index;if (index === undefined) return;const todoItem = todos[index];const textSpan = e.target.closest(".text");if (textSpan) {// 创建输入框替换文本const input = document.createElement("input");input.type = "text";input.value = todoItem.text;input.classList.add("edit-input");// 替换文本节点为输入框textSpan.parentNode.replaceChild(input, textSpan);input.focus();// 失去焦点或回车保存const saveEdit = () => {const newText = input.value.trim();if (newText) {todos[index].text = newText;renderTodos();} else {// 空文本则删除todos.splice(index, 1);renderTodos();}};input.addEventListener("blur", saveEdit);input.addEventListener("keydown", (e) => {if (e.key === "Enter") saveEdit();if (e.key === "Escape") renderTodos(); // 取消编辑});}
});

常见问题解答(FAQ)

Q1:querySelectorAll与getElementsByClassName的性能差异?

AgetElementsByClassName(实时HTMLCollection)在频繁DOM操作场景性能较差(因实时更新),querySelectorAll(非实时NodeList)性能更稳定,推荐优先使用querySelectorAll

Q2:事件委托中如何精确匹配目标元素?

A:使用e.target.matches(selector)e.target.closest(selector)

ul.addEventListener("click", (e) => {if (e.target.matches("li.item")) { // 精确匹配li.itemconsole.log("点击了列表项");}const target = e.target.closest(".todo-item"); // 查找最近的祖先元素if (target) { /* 处理逻辑 */ }
});
Q3:localStorage存储对象时为什么需要JSON序列化?

A:因为localStorage仅支持字符串存储,直接存储对象会转为"[object Object]",失去数据结构。使用JSON.stringify(obj)转为JSON字符串,读取时用JSON.parse()恢复对象。

Q4:如何监听localStorage的变化?

A:通过window.addEventListener("storage", callback),但仅在同域下的其他标签页修改时触发,当前标签页修改不会触发。

Q5:表单提交时如何阻止默认行为并使用AJAX提交?

A

form.addEventListener("submit", (e) => {e.preventDefault(); // 阻止默认提交if (!form.checkValidity()) return; // 验证表单// 收集表单数据const formData = new FormData(form);// 或手动收集:Object.fromEntries(new FormData(form))// AJAX提交(fetch示例)fetch("/api/submit", {method: "POST",body: formData}).then(res => res.json()).then(data => console.log("提交成功:", data)).catch(error => console.error("提交失败:", error));
});
http://www.dtcms.com/a/292706.html

相关文章:

  • Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现水下鱼类识别(C#代码,UI界面版)
  • 深入浅出Proxy与Reflect:从“黑中介“到“数据管家“的进阶之路
  • 【openssl生成自签证书】
  • Redis持久化-AOF
  • OpenCV 零基础到项目实战 | DAY 1:图像基础与核心操作
  • UE5 UI 安全区
  • 基于springboot的医院资源管理系统(源码+论文)
  • nodejs:告别全局安装,npx 命令详解及其与 npm 的区别
  • 网络安全渗透攻击案例实战:某公司内网为目标的渗透测试全过程
  • 如何永久删除安卓设备中的照片(已验证)
  • 2025 年非关系型数据库全面指南:类型、优势
  • 【Android】Popup menu:弹出式菜单
  • 小玩 Lifecycle
  • imx6ull-系统移植篇17——linux顶层 Makefile(上)
  • ZooKeeper学习专栏(五):Java客户端开发(原生API)详解
  • map和set的应用与模拟实现
  • UNet改进(24):注意力机制-从基础原理到高级融合策略
  • LLC协议
  • 基于 fastapi 的 YOLO 批量目标检测 API:支持单图 / 文件夹自适应处理
  • 前端葵花宝典
  • 内核协议栈源码阅读(一) ---驱动与内核交互
  • Git的一些使用
  • Vue3 面试题及详细答案120道(31-45 )
  • API网关原理与使用场景详解
  • java学习 leetcode31 下一个排列
  • C语言:第11天笔记
  • ansible 批量 scp 和 load 镜像
  • Spring之【Bean工厂后置处理器】
  • PHP 8.0 超维意识编程终极指南(终篇)终极展望:PHP与宇宙意识融合跨维度架构模式超弦控制器增强版(1)
  • 最新植物大战僵尸杂交版最新版本2.5.1版,内置触屏+加速+全屏,附PC+安卓+iOS最全安装教程!