现代 DOM 节点与列表渲染:选择器、遍历和事件委托
本文目录
DOM 是浏览器暴露给 JavaScript 的文档对象模型。Node 可以表示元素、文本、注释等;Element 是元素节点的基类。nodeType、nodeName 和 nodeValue 是理解旧代码时有用的接口,新代码通常直接使用 querySelector、children、parentElement、firstElementChild 和 closest。
查询与遍历的边界
querySelector 返回第一个匹配元素,querySelectorAll 返回静态 NodeList;getElementsByClassName 等旧接口通常返回实时集合。若要在遍历时增删节点,静态快照更容易预测。查询不到元素时应处理 null,选择器来自用户输入时需谨慎转义或改用固定选择器。
const list = document.querySelector('[data-book-list]');
const cards = Array.from(list?.querySelectorAll('[data-book-id]') ?? []);
for (const card of cards) {
const title = card.querySelector('.book-title')?.textContent?.trim() ?? '';
console.log(title);
}
parentNode 可能返回 Document 或 DocumentFragment;parentElement 只返回父元素。childNodes 包含文本和注释节点,children 只包含元素。不要用“跳过空白文本”的手写循环去兼容已经不再支持的旧浏览器;先明确目标浏览器,再依照标准 API 写清楚行为。
属性、状态和安全文本
setAttribute 适合设置 HTML 属性,classList 适合增删类名,表单控件的 value/checked 等属性反映当前运行时状态。dataset 用于读取 data-* 元数据,但所有来自 DOM 的值仍然是不可信字符串。用户可见文本用 textContent,不要将列表内容拼成 HTML 字符串。
function renderItems(container, items) {
const fragment = document.createDocumentFragment();
for (const item of items) {
const li = document.createElement('li');
li.dataset.itemId = String(item.id);
li.textContent = item.title;
fragment.append(li);
}
container.replaceChildren(fragment);
}
如果内容确实是富文本,需经过维护中的 sanitizer 并设置协议/元素/属性 allowlist。动态创建链接时先解析 URL,限制协议和允许的主机,再赋给 href;不要将任意 HTML 字符串传给 innerHTML。
动态列表使用事件委托
把一个监听器放在稳定的列表容器上,通过 event.target.closest 找到最近的操作元素;确认该元素仍在当前容器内,防止响应列表外的嵌套事件。不要为每个滚动或更新后新建的子节点重复绑监听。
function handleListClick(event) {
if (!(event.target instanceof Element)) return;
const button = event.target.closest('button[data-action]');
if (!button || !list.contains(button)) return;
const row = button.closest('[data-item-id]');
if (button.dataset.action === 'remove' && row) row.remove();
}
list.addEventListener('click', handleListClick);
页面卸载、组件销毁或容器替换时解除监听。也可以将 AbortController 的 signal 传给 addEventListener,在生命周期结束时 abort 一组监听器。交互使用原生 button/link 并提供清楚的可访问名称,避免用任意 div 模拟控件。
验收要点
- 查询失败是否有明确空状态,而非直接访问 null?
- 循环处理静态快照还是实时集合,增删是否改变遍历结果?
- 用户文本是否经过 HTML sink,DOM 数据是否被当作可信权限?
- 事件委托是否限定在容器内部,并使用语义化控件?
- 目标浏览器范围是否明确,不再保留无用的 IE 兼容层?
参考:MDN Document.querySelector、MDN querySelectorAll、MDN Node.childNodes、MDN Element.children、MDN Event delegation、MDN addEventListener。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。