DOM(Document Object Model,文档对象模型)是浏览器把HTML文档解析后生成的一棵树形结构,JavaScript正是通过这棵树来读取和修改页面的。可以说,几乎所有网页交互效果,点击展开菜单、表单实时校验、列表动态刷新,底层都是DOM操作在起作用。要写出流畅的网页交互,就必须理解DOM的结构规律,掌握选取节点、修改内容、绑定事件这三项核心能力。

一、理解DOM树:节点是操作的基本单位
浏览器解析HTML时,会把文档中的每一个标签、每一段文字甚至每一个换行都映射为树上的一个节点。常见的节点类型有三种:元素节点(如<div>)、文本节点(标签之间的文字)和属性节点(如class、id)。整个文档的根节点是document,它是访问整棵树的入口。
理解父子关系是掌握DOM的关键。下面这段结构中,ul是li的父节点,两个li互为兄弟节点:
<ul id="list"> <li>第一项</li> <li>第二项</li> </ul>
借助parentNode、children、nextElementSibling这些属性,可以在树上自由移动。另外要注意,childNodes返回的是包含文本节点在内的所有子节点,而children只返回元素节点,初学者经常混淆这两者,导致取到空白的文本节点后程序报错。
二、选取与修改:DOM操作的两大基本功
现代JavaScript提供了几组高效的选取方法。document.querySelector接收CSS选择器,返回第一个匹配的元素;querySelectorAll返回所有匹配元素的静态集合。相比老式的getElementById和getElementsByTagName,前者的选择器写法更灵活,是目前的推荐用法。
拿到元素之后,读写内容和属性主要靠以下几个接口:textContent处理纯文本,innerHTML解析HTML片段,setAttribute和getAttribute处理属性,classList提供add、remove、toggle三个方法来切换样式类。看一个综合例子:
// 选取元素并修改内容
const box = document.querySelector('#box');
box.textContent = '内容已更新';
box.classList.add('active');
// classList.toggle 适合做开关效果,比如点击显示或隐藏面板
btn.addEventListener('click', function () {
panel.classList.toggle('hidden');
});这里有一个必须重视的安全问题:innerHTML会解析字符串中的HTML标签,如果把用户输入的内容直接拼进去,就可能被注入恶意脚本,也就是常说的XSS攻击。所以处理用户输入时,优先使用textContent,只有在确认内容安全时才使用innerHTML。此外,如果只是更新文字,textContent的性能也明显优于innerHTML,因为它跳过了解析HTML的环节。
三、动态创建节点:让页面内容活起来
动态添加内容是网页交互最常见的场景,比如留言板的追加评论、购物车的添加商品。标准做法是用document.createElement创建元素,设置好内容和属性后,再通过appendChild或insertBefore插入到指定位置。删除节点则调用父节点的removeChild方法,或者直接使用元素自身的remove方法。
// 创建一个新的列表项并插入
const li = document.createElement('li');
li.textContent = '新增的一条数据';
li.className = 'item';
const list = document.querySelector('#list');
list.appendChild(li);
// 删除最后一个列表项
const last = list.lastElementChild;
if (last) {
last.remove();
}当需要一次性插入大量节点时,要注意性能细节。每次appendChild都可能触发浏览器回流和重绘,循环插入一千个节点就意味着上千次重排。更好的方案是先用DocumentFragment作为临时容器,把所有新节点攒在里面,最后一次性挂到DOM树上,这样只触发一次重排,性能提升非常明显。
四、事件机制:交互的触发引擎
DOM操作解决的是改变页面,事件机制解决的是什么时候改变。通过addEventListener可以为元素绑定事件,第三个参数可以控制监听发生在捕获阶段还是冒泡阶段。事件流分三个阶段:捕获(从根往目标传播)、目标、冒泡(从目标往根返回)。绝大多数场景用默认的冒泡阶段就够了。
事件冒泡带来一个非常实用的技巧——事件委托。如果一个ul下面有一百个li都要响应点击,不必给每个li都绑定监听器,只需在ul上绑定一次,通过event.target判断实际被点击的是哪个子元素。这样既节省内存,又能自动兼容后续动态添加的元素。下面是一个完整的动态表格增删示例,综合运用了创建节点和事件委托:
const tbody = document.querySelector('#tbl tbody');
const addBtn = document.querySelector('#addBtn');
const nameInput = document.querySelector('#nameInput');
let id = 0;
// 添加一行
addBtn.addEventListener('click', function () {
const name = nameInput.value.trim();
if (!name) return;
id++;
const tr = document.createElement('tr');
tr.innerHTML = '<td>' + id + '</td><td>' + name + '</td><td><button data-act="del">删除</button></td>';
tbody.appendChild(tr);
nameInput.value = '';
});
// 事件委托处理删除,动态新增的行也能生效
tbody.addEventListener('click', function (e) {
if (e.target.dataset.act === 'del') {
e.target.closest('tr').remove();
}
});上面代码里,删除按钮的监听绑在tbody上,即使之后新增了一百行,也不需要重新绑定任何监听器,这就是委托的价值。另外要养成一个好习惯:不需要的监听器用removeEventListener及时移除,尤其是在单页应用中,残留的监听器是内存泄漏的常见来源。
五、写出高效DOM代码的几条原则
第一,减少对布局属性的频繁读取。代码中交替读取offsetHeight再修改样式,会迫使浏览器反复回流。正确做法是先集中读取,再集中修改。第二,批量操作用DocumentFragment或拼接字符串后一次赋值。第三,样式修改尽量通过切换class完成,把复杂样式写在CSS里,JavaScript只负责状态切换,逻辑更清晰。第四,操作前先判断节点是否存在,配合?.可选链可以大幅减少空指针报错。掌握这些原则,配合对DOM树结构和事件流的深入理解,就能写出既流畅又易维护的网页交互代码。
JavaScript DOM操作网页交互事件监听修改时间:2026-09-14 11:14:35