JavaScript DOM 核心操作:从内容到节点的实战指南
(2025–2026 年仍然最常用的原生 DOM API 写法与现代最佳实践)
以下内容按实际开发中最常用的操作顺序组织,从查找 → 创建 → 修改 → 插入 → 删除 → 属性/样式 → 事件 → 遍历,并标注常见陷阱与现代替代方案。
1. 查找节点(最常用 Top 10 方法)
| 优先级 | 方法 | 说明 | 返回值 | 现代推荐场景 | 性能备注 |
|---|---|---|---|---|---|
| 1 | document.querySelector(selector) | CSS 选择器,第一个匹配 | Element 或 null | 几乎所有场景首选 | 较慢但最灵活 |
| 2 | document.querySelectorAll(selector) | 所有匹配 | NodeList(静态) | 需要多个元素时首选 | 静态快照,不随 DOM 变化 |
| 3 | element.querySelector(…) | 从某个元素开始查找 | Element 或 null | 局部查找,性能更好 | — |
| 4 | getElementById(id) | 通过 id,最快 | Element 或 null | 有明确 id 时首选 | 最快 |
| 5 | getElementsByClassName(class) | 通过类名 | HTMLCollection(动态) | 旧代码常见,新代码少用 | 动态集合,慎用 |
| 6 | getElementsByTagName(tag) | 通过标签名 | HTMLCollection(动态) | 少用 | 动态集合 |
| 7 | element.closest(selector) | 向上查找最近的匹配祖先 | Element 或 null | 事件委托中非常常用 | 非常实用 |
| 8 | element.matches(selector) | 判断当前元素是否匹配选择器 | boolean | 事件委托判断类型时用 | — |
| 9 | document.getElementsByName(name) | 通过 name 属性(表单元素常见) | NodeList | 极少用 | — |
| 10 | element.children | 只获取直接子元素(不含文本节点) | HTMLCollection | 常用于只关心元素子节点 | 动态 |
2025–2026 推荐优先级口诀:id → querySelector → querySelectorAll → closest → children
2. 创建与克隆节点
// 创建元素
const div = document.createElement('div');
div.textContent = 'Hello';
div.className = 'box active';
div.dataset.id = '123';
// 创建文本节点(最常用)
const text = document.createTextNode('纯文本内容');
// 克隆节点(深度克隆最常用)
const clone = original.cloneNode(true); // true = 深克隆(包含子节点)
const shallow = original.cloneNode(false); // 只克隆自身
3. 修改内容(四种主流方式对比)
| 方法 | 修改方式 | 是否解析 HTML | 会清除原有事件监听? | 推荐场景 | 陷阱 |
|---|---|---|---|---|---|
element.innerHTML | 替换全部内容 | 是 | 是 | 需要插入 HTML 结构时 | XSS 风险、性能差、丢失事件 |
element.textContent | 纯文本 | 否 | 否 | 最推荐(安全、高性能) | 不会解析标签 |
element.innerText | 考虑 CSS 显示的文本 | 否 | 否 | 需要考虑样式隐藏的内容 | 性能最差(会重排) |
element.insertAdjacentHTML(position, html) | 在指定位置插入 HTML | 是 | 否(只影响新内容) | 局部插入 HTML 时最佳 | 仍需防范 XSS |
位置参数(insertAdjacent* 方法通用):
'beforebegin'→ 元素前面'afterbegin'→ 元素内部最前面'beforeend'→ 元素内部最后面(最常用)'afterend'→ 元素后面
4. 插入节点(现代最推荐的 5 种方式)
// 方式1:最常用、最直观(2020年后主流)
parent.append(child); // 末尾添加(可多个)
parent.prepend(child); // 开头添加
parent.before(child); // 兄弟前插入
parent.after(child); // 兄弟后插入
// 方式2:经典但已被取代
parent.appendChild(child); // 只支持一个节点
parent.insertBefore(newNode, referenceNode);
// 方式3:批量插入(性能最好)
const fragment = document.createDocumentFragment();
fragment.append(div1, div2, div3); // 不会引起多次重排
parent.appendChild(fragment); // 一次性插入
5. 删除 & 清空节点
// 删除自身(最常用)
element.remove(); // 现代首选
// 旧方式(仍兼容)
parent.removeChild(element);
// 清空所有子节点(三种方式对比)
element.innerHTML = ''; // 最快,但有 XSS 风险
while (element.firstChild) {
element.removeChild(element.firstChild);
}
element.replaceChildren(); // 现代最推荐(2020+),干净、高效
6. 属性操作(attr vs property)
| 操作类型 | 方法/属性 | 适用场景 | 注意事项 |
|---|---|---|---|
| 自定义属性 | element.dataset.xxx | data-* 属性 | 自动驼峰转换 |
| class | classList.add/remove/toggle/contains/replace | class 操作首选 | 比 className 好用 100 倍 |
| style | element.style.color = 'red' | 行内样式 | 优先用 class 代替 |
| 通用属性 | setAttribute / getAttribute / hasAttribute / removeAttribute | HTML 属性 | value、checked、disabled 等特殊 |
| DOM 属性 | element.value / checked / disabled | 表单元素的“当前状态” | 优先使用 DOM 属性而非 getAttribute |
7. 事件绑定(现代写法)
// 推荐写法(2020+)
button.addEventListener('click', (e) => {
// e.currentTarget vs e.target
// e.preventDefault()
// e.stopPropagation()
}, { once: true, passive: true }); // 优化选项
// 事件委托(性能最佳)
document.addEventListener('click', (e) => {
if (e.target.matches('.delete-btn')) {
// 处理删除
}
});
// 移除事件(必须是同一个函数引用)
const handler = () => console.log('clicked');
btn.addEventListener('click', handler);
btn.removeEventListener('click', handler);
8. 遍历节点(常用集合对比)
| 集合类型 | 是否动态 | 包含什么 | 推荐遍历方式 |
|---|---|---|---|
| childNodes | 动态 | 元素 + 文本 + 注释 | for…of 或 for 循环 |
| children | 动态 | 只元素 | for…of 或 for 循环 |
| querySelectorAll | 静态 | 只元素 | forEach / for…of |
| NodeList(部分) | 部分动态 | — | — |
9. 2025–2026 高频面试 + 真实项目避坑点
innerHTMLvstextContentvsinsertAdjacentHTML的 XSS 风险对比appendChildvsappend的参数差异(append 支持多个 + 字符串)- 事件委托中
e.targetvse.currentTargetvsthis的区别 className = ' '会清空所有 class,推荐用classListdataset属性自动驼峰:data-user-id → element.dataset.userId- Fragment 批量插入 vs 多次 appendChild 的性能差距(可达 10–100 倍)
remove()方法 IE 不支持(需 polyfill 或用 parent.removeChild)closest()从自身开始找(包含自己)
需要哪一部分更详细的完整示例代码(如:动态表格增删改查、拖拽排序、事件委托实现 tab 切换、虚拟列表简化版等),或者想看某个具体场景的对比写法?可以告诉我,我再展开。