JavaScript DOM 操作
DOM(Document Object Model,文档对象模型)是浏览器把 HTML 解析后生成的"对象树"。JS 通过 DOM API 可以读取、修改、增删页面元素——这就是"动态网页"的本质。这一章我们学习怎么用 JS 操作 DOM。
1. 什么是 DOM
浏览器加载 HTML 时,会把它解析成一棵由节点组成的树。比如:
<!-- HTML -->
<html>
<head><title>页面</title></head>
<body>
<div id="main">
<p>段落</p>
</div>
</body>
</html>
<!-- 对应的 DOM 树(简化):
document
|
html
/ \
head body
| |
title div#main
| |
"页面" p
|
"段落" -->JS 通过 document 这个全局对象访问整棵树。window.document 是它的全称。
2. 选中元素(querySelector)
操作 DOM 的第一步是拿到元素。现代 JS 用 querySelector 系列,语法和 CSS 选择器完全一样:
<!-- 假设页面有这些元素 -->
<button id="btn">点我</button>
<div class="item">第一项</div>
<div class="item">第二项</div>
<input name="username" />
<script>
// 1. querySelector:CSS 选择器,返回第一个匹配
const btn = document.querySelector("#btn");
const item = document.querySelector(".item");
const input = document.querySelector("input[name=username]");
// 2. querySelectorAll:返回所有匹配(NodeList)
const items = document.querySelectorAll(".item");
console.log(items.length); // 2
items.forEach(el => console.log(el.textContent));
// 3. 老 API(也能用,但不如 querySelector 直观)
const btn2 = document.getElementById("btn");
const items2 = document.getElementsByClassName("item"); // HTMLCollection
const buttons = document.getElementsByTagName("button");
</script>选择器优先级:
querySelector("#id"):按 ID 选,常用。querySelector(".class"):按 class 选。querySelector("div > p:first-child"):复杂 CSS 选择器都支持。querySelectorAll(".item"):选多个,返回 NodeList(可以用 forEach)。
注意:getElementsByClassName 返回的是动态的 HTMLCollection(DOM 改了它就跟着变),querySelectorAll 返回静态的 NodeList。新项目统一用后者。
3. 修改内容
const el = document.querySelector("#title");
// 1. textContent:纯文本(推荐!安全、快)
el.textContent = "新标题";
// 2. innerHTML:可以包含 HTML 标签(注意 XSS 风险)
el.innerHTML = "<strong>新标题</strong>";
// 3. 区别演示
const userInput = "<img src=x onerror=alert('XSS')>";
el.textContent = userInput; // ✅ 安全:原样显示文本
// el.innerHTML = userInput; // ❌ 危险:会执行里面的脚本!
// 读取内容
console.log(el.textContent); // 读取纯文本
console.log(el.innerHTML); // 读取 HTML
// hidden 属性:显示/隐藏
el.hidden = true; // 等价 display: none
el.hidden = false;XSS 警告:innerHTML 会解析 HTML 字符串,如果内容来自用户输入,可能被注入恶意脚本(经典 XSS 攻击)。规则是——凡是用户输入的内容,一律用 textContent,不要拼到 innerHTML。
4. 修改属性
const img = document.querySelector("img");
// 1. 标准 attribute:直接用属性
console.log(img.src); // 读取
img.alt = "图片描述"; // 设置
console.log(img.id);
// 2. 任意 attribute:getAttribute / setAttribute
console.log(img.getAttribute("data-id"));
img.setAttribute("data-id", "100");
img.removeAttribute("title");
// 3. dataset:操作 data-* 属性(超常用)
const user = document.querySelector("[data-user-id]");
user.dataset.userId = "200"; // HTML 里是 data-user-id
console.log(user.dataset.userId); // "200"(自动驼峰)
// 4. class 操作:用 classList,不要直接改 className
const el = document.querySelector(".box");
el.classList.add("active"); // 添加
el.classList.remove("box"); // 删除
el.classList.toggle("dark"); // 切换:有则删,无则加
el.classList.contains("active"); // 是否包含(返回布尔)
el.classList.replace("old", "new"); // 替换dataset 是处理 data-* 自定义属性的标准方式——HTML 里写 data-user-id,JS 里读 el.dataset.userId(自动转驼峰)。在列表项上存数据、点击时取出,这种模式很常用。
5. 修改样式
const el = document.querySelector("#box");
// 1. 单独改 style:语法 el.style.属性名
el.style.color = "red";
el.style.fontSize = "20px"; // 注意:CSS 是 font-size,JS 用驼峰
el.style.backgroundColor = "#f0f0f0";
el.style.display = "none"; // 隐藏
// 读取计算样式(只读)
const computed = getComputedStyle(el);
console.log(computed.height); // "200px"
// 2. 推荐:用 class 切换样式,而不是改 style
// CSS:
// .highlight { background: yellow; }
// .hidden { display: none; }
el.classList.add("highlight");
el.classList.add("hidden");
// 3. 改多个样式:批量设置
Object.assign(el.style, {
color: "white",
background: "blue",
padding: "10px 20px"
});最佳实践:用 class 切换样式,不要直接改 style。原因:CSS 和 JS 解耦,主题切换、动画、维护都更方便。直接改 style.xxx 只适合动态计算的值(如拖拽位置、图表宽度)。
6. 创建与插入元素
// 1. 创建元素
const p = document.createElement("p");
p.textContent = "我是新段落";
p.classList.add("msg");
// 2. 插入到 DOM 树
document.body.appendChild(p); // 加到 body 末尾
// 插入到指定容器
const container = document.querySelector("#list");
container.appendChild(p);
// 3. 更现代的 API:append / prepend / before / after
container.append(p); // 末尾加,可传多个
container.prepend(p); // 开头加
container.before(p); // 元素前面加
container.after(p); // 元素后面加
// 4. 删除元素
p.remove(); // 直接删(推荐)
// 老写法
// p.parentNode.removeChild(p);
// 5. 克隆
const clone = p.cloneNode(true); // true 表示深克隆(含子节点)现代 API append/prepend/before/after 比老的 appendChild/insertBefore 更直观,而且支持插入多个参数和文本节点。新项目优先用现代 API。
7. 批量构建列表
从接口拿到一组数据,渲染成列表/表格,是前端最高频的操作:
// 批量创建列表项(常见场景)
// ❌ 老写法:用字符串拼接(易出错、有 XSS 风险)
const html = users.map(u => `<li>${u.name}</li>`).join("");
ul.innerHTML = html;
// ✅ 推荐:用 DOM API
const ul = document.querySelector("#list");
const users = [{ name: "小明" }, { name: "小红" }];
users.forEach(u => {
const li = document.createElement("li");
li.textContent = u.name;
ul.appendChild(li);
});
// ✅ 性能优化:用 DocumentFragment 批量插入
const frag = document.createDocumentFragment();
users.forEach(u => {
const li = document.createElement("li");
li.textContent = u.name;
frag.appendChild(li);
});
ul.appendChild(frag); // 只触发一次重排性能要点:DOM 操作是"重活",每次插入都触发重排(reflow)和重绘(repaint)。批量插入时一定要用 DocumentFragment(或字符串模板一次性 innerHTML),避免在循环里反复 append。
8. 表单读写
// 读取表单值
const input = document.querySelector("#username");
console.log(input.value); // 输入框的值
const checkbox = document.querySelector("#agree");
console.log(checkbox.checked); // 布尔,是否勾选
const select = document.querySelector("#city");
console.log(select.value); // 选中的 value
// 设置值
input.value = "默认名字";
checkbox.checked = true;
// 监听表单提交
const form = document.querySelector("form");
form.addEventListener("submit", (e) => {
e.preventDefault(); // 阻止默认提交刷新页面
const data = new FormData(form);
console.log(Object.fromEntries(data)); // { username: "...", ... }
});FormData 是处理表单的现代方案——一次性拿到所有字段的键值对,无需手动遍历。配合 Object.fromEntries 直接转成对象,提交 AJAX 时极其方便。
9. 实战:待办列表
<input id="input" placeholder="输入待办" />
<button id="add">添加</button>
<ul id="list"></ul>
<script>
const input = document.querySelector("#input");
const addBtn = document.querySelector("#add");
const list = document.querySelector("#list");
addBtn.addEventListener("click", () => {
const text = input.value.trim();
if (!text) return;
const li = document.createElement("li");
li.textContent = text; // 用 textContent 防 XSS
// 添加删除按钮
const delBtn = document.createElement("button");
delBtn.textContent = "删除";
delBtn.addEventListener("click", () => li.remove());
li.appendChild(delBtn);
list.appendChild(li);
input.value = ""; // 清空输入框
input.focus();
});
</script>这个例子融合了选元素、读输入、创建节点、添加事件、删除节点——是 DOM 操作的"全家桶"练习。把它写出来跑通,DOM 就基本入门了。
10. 性能建议
- 批量操作:循环里 append 性能差,用 Fragment 或一次性 innerHTML。
- 减少重排:多次改样式,先用 class 切换;读尺寸前先缓存(避免反复触发重排)。
- 事件委托:列表里很多元素要监听同样的事件,只在父元素加一个监听,通过
e.target判断来源。 - 虚拟 DOM:React/Vue 用虚拟 DOM 解决了"手写 DOM 操作繁琐"的问题,但理解原生 DOM 是看懂框架的前提。
小结
- DOM 是浏览器生成的"对象树",JS 通过
document访问。 - 选元素用
querySelector/querySelectorAll。 - 改内容用
textContent(安全),改样式用 class。 - 增删用
createElement+append,删除用remove()。 - 批量插入用 Fragment,避免重排性能问题。
下一篇我们看事件——让网页真正"响应"用户的操作。
← 上一篇 JavaScript 字符串
下一篇 JavaScript 事件 →