01 · NOTES / WebGIS 开发
第 14 节 · DOM 操作
2026 年 9 月 1 日
第 14 节 · DOM 操作
📌 版本信息:基于 DOM Living Standard(2026-08-29 核对) 📚 来源:现代 JS 教程 · 文档 | MDN DOM 入门 | 本地教材
resources/01-web-basics/modern-js-tutorial/2-ui/
一、这一节的目标
- 理解 DOM 是什么:HTML 被 JS 摸得到的样子
- 掌握查找元素的两大 API:
querySelector/querySelectorAll - 掌握增删改:
createElement/append/remove/innerHTML - 会改样式和类:
classList三件套 - 完成动态待办列表——"数据变化 → 页面跟着变"的第一次完整体验
二、DOM 是什么
一句话:浏览器把你的 HTML 解析成一棵"对象树",JS 通过这棵树读写页面。
<html> → <body> → <div id="app"> → <h1>、<ul> → <li>……
类比:HTML 是你写的设计图,DOM 是浏览器按图盖出来的真实大楼——JS 操作的是大楼(DOM),不是图纸(HTML 源码)。你在 DevTools Elements 面板里看到的就是这棵树(第 01 模块第 04 节见过)。
三、查找元素(会用两个就够)
// querySelector:按 CSS 选择器找"第一个"(最常用,一个 API 打天下)
const box = document.querySelector('#app'); // 按 id
const item = document.querySelector('.card'); // 按类
const btn = document.querySelector('button[type="submit"]'); // 任意 CSS 选择器
// querySelectorAll:找"所有",返回 NodeList(能 forEach,不是纯数组)
const items = document.querySelectorAll('.card');
items.forEach((el) => console.log(el.textContent));
// 也可以在任意元素上继续往下找(缩小范围)
const list = document.querySelector('#todo-list');
const firstDone = list.querySelector('.done');
💡 老代码里的
getElementById/getElementsByClassName还会见到,认识即可;新代码统一querySelector系列(一套选择器语法走天下)。
四、读写内容与属性
const el = document.querySelector('#title');
// 读/写文本(安全,推荐——内容会被当纯文本)
el.textContent = '新标题';
// 读/写 HTML(⚠️ 会解析标签:只能用于自己拼的安全内容,绝不能拼用户输入!XSS 攻击入口)
list.innerHTML = '<li>由 JS 生成的一行</li>';
// 读写属性
const link = document.querySelector('a');
link.href = 'https://leafletjs.com/';
link.setAttribute('target', '_blank');
link.getAttribute('href');
inputEl.value = '输入框的值'; // 表单元素用 .value(第 03 节埋的伏笔)
⚠️ innerHTML + 用户输入 = XSS 漏洞。用户输入一律用
textContent。这条纪律从今天开始养成。
五、增删元素
// 创建 + 装配 + 上树(标准三步)
const li = document.createElement('li'); // 1. 造(此时还悬浮在内存里)
li.textContent = '学完第 14 节'; // 2. 装内容/加类
li.classList.add('done');
list.append(li); // 3. 放进某个父元素的末尾
// 其他插入位
parent.prepend(el); // 插到最前
parent.before(el); // 插到某元素前面
parent.after(el); // 插到某元素后面
el.remove(); // 删除自己
list.innerHTML = ''; // 清空容器(暴力但常用)
六、classList:类名的增删改查
样式永远交给 CSS(第 04 节的选择器),JS 只负责切换类名——这是"结构与行为分离"的关键纪律:
el.classList.add('done'); // 加
el.classList.remove('done'); // 删
el.classList.toggle('done'); // 有则删无则加(开关,超好用)
el.classList.contains('done'); // 有没有
// 配合 CSS:
// li.done { text-decoration: line-through; color: #999; }
// JS 只切类,划线变灰是 CSS 的事——各司其职
直接改样式(el.style.color = 'red')能用但只适合极个别动态值(比如根据数据算出来的颜色),常规一律 classList。
七、性能初识:别在循环里反复碰 DOM
DOM 操作是 JS 里最贵的操作之一(跨过 JS/渲染的边界)。两种修正思路:
// ❌ 慢:100 次上树 = 100 次重排
for (const name of names) {
list.append(makeLi(name));
}
// ✅ 快:先拼好一整段,一次上树
list.innerHTML = names.map((n) => `<li>${n}</li>`).join('');
// (第 13 节的 map + join 组合在这里会师——这就是它最常见的用途)
八、动手跟练:14 · 动态待办列表
配套文件:02-web-basics/examples/14-动态待办列表.html
步骤:
- Live Server 打开:一个能增删、能勾选完成的待办列表
- 读代码:
render()函数是灵魂——每次数据变了就整个重画列表(数据驱动的雏形,React 的核心思想提前见面) - 完成 5 个 TODO:回车添加、点击切换完成态、删除按钮、剩余数量统计、清空已完成
- 观察:所有操作都没有"直接改某个 li",全靠"改数组 → render()"——体会这个模式
通关标准:
- 增删勾选全部正常
- 能口头说出"数据驱动渲染"的循环:操作数据 → 调 render() → 页面重画
- 能说出 innerHTML 拼用户输入的危险性
九、自测题
querySelector和querySelectorAll返回的东西有什么不同?- 为什么用户输入要用
textContent而不是innerHTML? - "JS 只切类名,样式交给 CSS"——这样分工的好处?
innerHTML = ''的作用是什么?有什么副作用?- 循环里逐个
append有什么问题?两种解法?
参考答案
- 前者返回单个元素(找不到是 null);后者返回 NodeList(类数组,可 forEach)。
- innerHTML 会把输入当 HTML 解析,
<img onerror=...>这类内容会执行——XSS 攻击;textContent 当纯文本处理,安全。 - 结构与表现分离:改样式不用动 JS 代码,改行为不用动 CSS,CSS 选择器(含伪类 :hover)还能白嫖。
- 清空容器内所有子元素;副作用是整个重建(事件监听如果直接绑在子元素上会丢——第 15 节的事件委托就是解药)。
- 每次上树都可能触发重排,性能差;解法①拼字符串一次 innerHTML,解法②用 DocumentFragment 一次性插入。
十、下一步
页面会画了,但还不会"响应点击" → 第 15 节:事件,让页面真正"活"起来。
TAGSweb