全面梳理 MDN Web Docs 中推荐的 JavaScript DOM 操作标准示例,涵盖元素查找、节点创建挂载、属性与类名管理、事件委托及官方经典 Shopping List 范例。
MDN 提供的 JavaScript DOM 操作标准示例有哪些
时间:2026-10-30
浏览:329
文章目录
一、DOM 树模型与标准节点认知
在深入具体代码前,MDN 首先通过结构模型向开发者阐明:浏览器在解析 HTML 文档后,会将其转换为由节点(Node)构成的树状层次结构,即 DOM 树。在 JavaScript 中,所有页面元素、文本内容甚至属性都是这棵树上的一个节点。
理解标准 DOM 操作的核心,在于明确不同节点类型之间的职责。Document 是整棵树的访问入口,Element(元素节点)负责承载 HTML 标签与布局结构,而标签内部的纯文字则被封装在 Text(文本节点)中。MDN 强调,现代 DOM 操作的一大准则是区分“元素操作”与“纯文本注入”,从而在根源上避免安全缺陷。
二、标准示例一:元素查询与选择范式
在 MDN 文档体系中,检索页面元素被归类为所有交互的第一步。在早期的 JavaScript 教学中,经常能看到 document.getElementById() 与 document.getElementsByTagName()。而在现代 MDN 标准教程中,推荐采用统一基于 CSS 选择器语法的 querySelector 系列 API。
1. 单个与多个元素的标准查询
当需要匹配页面中符合规则的第一个元素时,使用 document.querySelector();当需要批量匹配所有符合规则的元素时,使用 document.querySelectorAll()。
// 1. 匹配 ID 选择器(返回符合条件的第一个 Element,未找到返回 null)
const heading = document.querySelector('#main-heading');
// 2. 匹配类选择器或复合选择器
const submitBtn = document.querySelector('button.btn-primary');
// 3. 批量匹配元素(返回静态 NodeList 集合)
const items = document.querySelectorAll('ul.todo-list > li');
// 标准遍历 NodeList
items.forEach((item, index) => {
console.log(`第 ${index + 1} 项内容: ${item.textContent}`);
});
许多初学者常疑惑:既然旧方法 getElementsByClassName() 执行性能略快,为什么 MDN 仍全面推荐 querySelectorAll()?
原因在于“实时性(Live)”带来的遍历陷阱。getElementsByClassName() 返回的是动态 HTMLCollection,当在循环体内向页面增删带有该类名的元素时,集合长度(length)会随之动态改变,极易引发死循环或索引漂移。而 querySelectorAll() 返回的是静态快照(Static NodeList),并且原生支持 .forEach() 迭代方法,逻辑可预测性更强。
三、标准示例二:节点的动态创建、配置与挂载
MDN 在“动态操作页面(Manipulating documents)”教学中,给出了极其严格且通用的节点创建流程,通常遵循四步法:创建孤岛节点 → 配置属性与文本 → 绑定必要事件 → 挂载至主文档树。
2. 基础创建示例:动态生成列表项
// 获取目标容器
const listContainer = document.querySelector('#product-list');
// 1. 创建 <li> 节点
const newListItem = document.createElement('li');
// 2. 设置内容与属性
newListItem.textContent = '高质量降噪耳机';
newListItem.classList.add('list-item', 'active');
newListItem.setAttribute('data-id', 'sku-1002');
// 3. 挂载到容器中
listContainer.appendChild(newListItem);
3. 现代 API 扩展:appendChild 与 append 的标准选择
在传统示例中,MDN 使用 Node.appendChild()。而在现代规范更新后,MDN 同步引入了 Element.append(),两者存在明确的特性差异:
appendChild()仅接受一个Node对象,并返回被追加的该节点。append()支持同时传入多个参数,不仅支持Node节点,还直接支持插入字符串(内部会自动转换为 Text 节点),且没有返回值。
// 现代 append() 标准语法:一次性插入多个元素与文本
const badge = document.createElement('span');
badge.textContent = '热销';
badge.className = 'badge';
// 同时追加 DOM 节点与文本内容
newListItem.append(badge, '(限时特惠中)');
4. 批量插入优化示例:DocumentFragment
当需要连续插入成百上千个节点时,若每次循环都直接调用 container.appendChild(),会导致浏览器频繁触发重排(Reflow)与重绘(Repaint)。MDN 在性能优化指导中给出的标准范例是使用轻量级的文档片段:document.createDocumentFragment()。
const fragment = document.createDocumentFragment();
const dataList = ['JavaScript', 'TypeScript', 'Node.js', 'Web API'];
dataList.forEach((tech) => {
const li = document.createElement('li');
li.textContent = tech;
fragment.appendChild(li); // 此时仍在内存中操作,不引起页面重绘
});
// 将包含所有子节点的 fragment 一次性挂载至主树
listContainer.appendChild(fragment);
四、标准示例三:内容注入安全性与 innerHTML 辨析
在 DOM 操作教学中,如何更新容器内部内容是一个不可回避的问题。MDN 在其安全指南与 DOM 教程中明确指出了 innerHTML 与 textContent 的边界。
许多教程常常提出“永远不要使用 innerHTML”的论断,这一说法看似正确,但不够严谨。MDN 给出的客观建议是:
- 什么时候可以使用:当内容是硬编码的静态 HTML 片段,且内部不需要维持事件绑定时,或者用来一行代码清空容器(
element.innerHTML = ''),此时使用innerHTML是极为简练高效的。 - 什么时候严禁使用:凡是内容中混入了来自输入框、URL 参数、服务端未受信任返回的动态字符串,严禁直接拼接进
innerHTML,必须改用textContent或document.createTextNode()。
五、标准示例四:属性控制与 classList 样式管理
修改元素样式和状态是 DOM 操作中最频繁的需求。MDN 强调将“结构、样式与行为解耦”,反对在 JavaScript 中大段编写行内 style 属性。
1. 标准类名控制器:classList
MDN 规范指出,控制样式的最佳实践是在 CSS 文件中预定义好类选择器,并在 JavaScript 中通过 Element.classList 接口来增删状态类。
const panel = document.querySelector('.info-panel');
// 添加样式类
panel.classList.add('visible', 'highlight');
// 移除样式类
panel.classList.remove('hidden');
// 切换样式类(存在则移除,不存在则添加)
panel.classList.toggle('active');
// 条件式切换(第二个布尔参数:true 时添加,false 时移除)
const isOnline = checkConnection();
panel.classList.toggle('online-mode', isOnline);
// 判断是否包含特定样式类
if (panel.classList.contains('visible')) {
console.log('面板正处于展示状态');
}
2. 自定义数据属性的标准读取:dataset
HTML5 允许在元素上定义以 data- 为前缀的自定义属性。MDN 示例展示了如何使用 dataset 接口实现 JavaScript 与 HTML 标记的解耦传参:
// HTML: <div id="user" data-user-id="9527" data-role-type="admin"></div>
const userEl = document.querySelector('#user');
// 读取属性(kebab-case 自动映射为 camelCase 驼峰命名)
console.log(userEl.dataset.userId); // 输出: "9527"
console.log(userEl.dataset.roleType); // 输出: "admin"
// 写入或更新自定义属性
userEl.dataset.status = 'active'; // 相当于添加 data-status="active"
六、标准示例五:事件监听与高效“事件委托”范式
响应用户交互是 DOM 编程的核心。MDN 在“Introduction to events”章节中彻底规范了事件绑定的标准写法,即使用 EventTarget.addEventListener()。
1. 基础事件监听规范
MDN 强烈建议避免使用 HTML 内联 onclick="..." 或 DOM0 级属性 element.onclick = fn,因为它们无法为一个事件附加多个监听器,且容易造成覆盖。
const actionBtn = document.querySelector('#submit-action');
function handleClick(event) {
// 阻止表单默认提交刷新
event.preventDefault();
console.log('点击按钮触发:', event.target);
}
// 绑定标准监听
actionBtn.addEventListener('click', handleClick);
// 在必要时精确移除监听(必须引用同一个具名函数)
// actionBtn.removeEventListener('click', handleClick);
2. 经典进阶示例:事件冒泡与事件委托(Event Delegation)
在处理动态列表或大量子元素(如几十个表格行、列表项)时,若为每个子元素单独调用 addEventListener,不仅浪费内存,而且动态新增的子项将无法响应事件。
MDN 提供的标准解法是利用 DOM 的事件冒泡(Event Bubbling)机制:将监听器绑定在公共父级容器上,通过 event.target 判断真正被点击的子元素。
const parentList = document.querySelector('#dynamic-list');
// 只在父容器上绑定一次点击监听
parentList.addEventListener('click', (event) => {
// 使用 Element.closest() 准确定位被点击项是否属于目标列表项
const targetItem = event.target.closest('li.list-item');
if (!targetItem || !parentList.contains(targetItem)) {
return; // 点击落在列表外部或空白区域,忽略
}
// 判断是否点击了删除按钮
if (event.target.matches('button.delete-btn')) {
targetItem.remove(); // 直接调用现代 Element.remove() 移除节点
console.log('已成功删除列表项');
} else {
targetItem.classList.toggle('selected');
}
});
七、MDN 官方经典综合示例:Shopping List 实战剖析
在 MDN 教程的“Manipulating documents”章节末尾,官方提供了一个标志性的实战综合演练——动态购物清单(Shopping List)。该范例紧凑而完整地组合了输入框读取、节点动态构建、嵌套结构挂载、事件驱动删除以及交互焦点管理。
1. 完整的 HTML 结构骨架
<div class="shopping-app">
<h1>我的购物清单</h1>
<div class="input-group">
<label for="item-input">输入商品名:</label>
<input type="text" id="item-input" placeholder="例如:鲜牛奶" />
<button id="add-btn">添加到清单</button>
</div>
<ul id="shopping-list"></ul>
</div>
2. MDN 官方标准 JavaScript 逻辑实现
// 1. 获取核心 DOM 引用
const list = document.querySelector('#shopping-list');
const input = document.querySelector('#item-input');
const button = document.querySelector('#add-btn');
// 2. 绑定添加事件处理函数
button.addEventListener('click', () => {
const itemText = input.value.trim();
// 空值校验
if (itemText === '') {
input.focus();
return;
}
// 3. 动态创建列表项及子组件
const listItem = document.createElement('li');
const textSpan = document.createElement('span');
const deleteBtn = document.createElement('button');
// 4. 组装子节点内容
textSpan.textContent = itemText;
deleteBtn.textContent = '删除';
deleteBtn.className = 'delete-item-btn';
// 5. 建立层级关系
listItem.append(textSpan, deleteBtn);
// 6. 为新建节点的删除按钮绑定专属销毁逻辑
deleteBtn.addEventListener('click', () => {
// 现代标准 API: 直接调用 element.remove()
listItem.remove();
});
// 7. 挂载到主列表,清空输入框并重置焦点
list.appendChild(listItem);
input.value = '';
input.focus();
});
- 焦点闭环(Focus Loop):添加或点击删除后,显式调用
input.focus(),极大地提升了无障碍(Accessibility)体验与键盘操作连贯性。 - 空值过滤与清理:通过
.trim()防御纯空格无意义数据。 - 声明式结构组装:先使用
createElement逐个创建li、span、button,再通过层级关系进行安全挂载,绝不使用脆弱的 HTML 字符串拼接。
八、MDN DOM 操作核心 API 与最佳实践全景总结
综观 MDN Web Docs 提供的所有 DOM 教学与参考指南,其标准示例的设计哲学可以归纳为四大原则:CSS 选择器统一化、节点构建安全化、交互逻辑委托化、样式表现类名化。下方的总结矩阵清晰展示了各核心任务下的推荐 API 与技术要点。
常见操作 API 速查表
| 操作领域 | 现代 MDN 推荐写法 | 早期/易混淆写法 | 主要优势与推荐理由 |
|---|---|---|---|
| 元素查找 | querySelector() / querySelectorAll() |
getElementById() / getElementsByTagName() |
统一选择器语法,返回静态 NodeList,支持 forEach 迭代,无动态变动隐患。 |
| 内容插入 | createElement() + textContent |
innerHTML = ... 拼接 |
自动转义纯文本,天然免疫 XSS 漏洞,保留容器内既有 DOM 节点与事件绑定。 |
| 节点挂载 | parent.append(el1, el2) |
parent.appendChild(el1) |
可一次性传入多个节点及字符串,语法更现代化、更灵活。 |
| 样式管理 | el.classList.add() / toggle() |
el.style.cssText = ... |
将表现形式交给 CSS 集中管理,JS 仅负责状态开关,保持代码高可维护性。 |
| 节点删除 | element.remove() |
parent.removeChild(element) |
节点直接调用自我删除,无需冗余获取 parentNode 引用。 |
| 事件交互 | 父级 addEventListener (事件委托) |
给每个子元素遍历绑定 onclick |
大幅降低内存占用,且能天然支持后续动态新增子项的点击响应。 |
掌握 MDN 提供的这些标准示例,能够让开发者在脱离各类前端框架(如 React、Vue)的情况下,编写出执行效率高、代码结构健壮且符合 Web 安全规范的原生 JavaScript 应用。这些标准范式同样是深入理解现代框架虚拟 DOM 与响应式渲染机制的坚实基石。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。