JavaScript DOM操作源代码:元素增删改查与事件绑定实例

作者:页面旅途时间:2026-11-02 09:17:24浏览:200

深入解析原生 JavaScript DOM 操作核心技术,通过可运行的源代码实例,系统剖析元素增、删、改、查 API 原理、性能优化方法及现代事件委托绑定实践。

分类:Web开发技术 • 阅读时长:约 10 分钟 • 技术栈:原生 JavaScript (ES6+)
JavaScript DOM操作源代码:元素增删改查与事件绑定实例
DOM(文档对象模型)是浏览器将 HTML 结构转化为 JavaScript 可操作对象的核心接口。掌握原生的“增、删、改、查”与事件绑定机制,是编写高性能、轻量级前端逻辑的基本功。本文不依赖任何第三方类库,直接提供经过测试的完整源代码实例,系统拆解节点生命周期的每一个操作步骤与内存管理要点。

一、元素查询(查):精准定位 DOM 节点

在对页面元素进行任何操作之前,首先需要获取目标节点的内存引用。现代 Web 规范主要提供了两类查找方式:基于专用属性的经典查询与基于 CSS 选择器的复合查询。

1. 核心查询 API 与适用场景

在实际工程中,document.getElementById 拥有专属的哈希索引表,查询速度最为迅速,适用于页面中唯一的顶级容器或关键控件;而 document.querySelector 与 document.querySelectorAll 能够使用包括属性选择器、伪类在内的任意 CSS 选择规则,灵活性极高。

// 1. 通过 ID 极速获取单个节点
const appContainer = document.getElementById('app');

// 2. 通过 CSS 选择器获取首个匹配的子节点
const activeItem = document.querySelector('.todo-list .todo-item.active');

// 3. 获取所有匹配节点的静态集合(NodeList)
const allItems = document.querySelectorAll('.todo-list .todo-item');

// 遍历 NodeList(现代浏览器原生支持 forEach)
allItems.forEach((item, index) => {
  console.log(`第 ${index + 1} 项内容:`, item.textContent.trim());
});
JavaScript DOM操作源代码:元素增删改查与事件绑定实例
图 1:DOM 树层级拓扑与常用查询 API 定位范围

2. 关键机制差异:静态 NodeList 与动态 HTMLCollection

理解两者的差异至关重要:通过 querySelectorAll 返回的是静态快照(Static NodeList),随后无论 DOM 结构如何增减,该集合不会自动变化;而通过 getElementsByClassName 或 getElementsByTagName 返回的是动态集合(Live HTMLCollection),DOM 的任何变更会实时映射回集合中。在循环遍历时,修改 DOM 如果依赖动态集合,极易因下标偏移产生无限循环或漏项错误,因此工程中更推荐使用静态快照 querySelectorAll。

二、元素创建与插入(增):构建与挂载新节点

向页面中添加新元素分为两个独立阶段:首先在内存中生成游离的节点并完成内容装配,然后通过挂载方法将其正式插入到文档渲染树中。

1. 元素创建与层级挂载步骤

使用 document.createElement(tagName) 创建新元素。该节点在挂载前完全存在于内存中,对其进行属性赋值或样式配置不会触发浏览器的重排(Reflow)与重绘(Repaint)。

JavaScript DOM操作源代码:元素增删改查与事件绑定实例
图 2:DOM 元素创建、组装与批量挂载操作流程

2. 单个节点挂载完整源代码

// 目标父容器
const listContainer = document.querySelector('#todoList');

// 步骤 1:在内存中创建元素节点
const newLi = document.createElement('li');

// 步骤 2:装配属性与样式类
newLi.className = 'todo-item priority-high';
newLi.dataset.id = 'task-2048';

// 步骤 3:注入安全文本与子元素
const textSpan = document.createElement('span');
textSpan.className = 'item-text';
textSpan.textContent = '完成财务报表审计工作';

const deleteBtn = document.createElement('button');
deleteBtn.className = 'btn-delete';
deleteBtn.textContent = '删除';

newLi.append(textSpan, deleteBtn);

// 步骤 4:挂载到真实 DOM 中(插入到末尾)
listContainer.append(newLi);

// 若需置顶插入,可使用 prepend
// listContainer.prepend(newLi);
⚡ 性能关键:循环插入必须使用 DocumentFragment
如果需要一次性插入数百甚至数千个列表项,千万不要在循环体中直接调用 parent.appendChild(),否则每一次调用都会触发生效渲染。应使用 document.createDocumentFragment() 创建轻量级的虚拟文档片段,将所有子节点先装配到片段中,最后单次追加到父级容器中,把数百次渲染合并为单次开销。
// 批量高性能插入范例
function renderBatchItems(container, dataList) {
  const fragment = document.createDocumentFragment();

  dataList.forEach(data => {
    const li = document.createElement('li');
    li.className = 'data-row';
    li.textContent = data.title;
    fragment.appendChild(li); // 仅在内存片段中暂存,不触发浏览器重排
  });

  // 一次性挂载整个片段,片段本身不会作为节点进入 DOM
  container.appendChild(fragment);
}

三、元素内容与属性修改(改):状态与表现同步

修改 DOM 主要包含两个维度:一是节点内部文本与 HTML 内容的变更;二是节点属性、类名与行内样式的动态切换。

1. 内容更新:textContent 与 innerHTML 的关键差异

修改元素内容最常用的属性是 textContent 与 innerHTML。它们不仅在语法行为上不同,更关系到整站的安全基线。

JavaScript DOM操作源代码:元素增删改查与事件绑定实例
图 3:文本内容更新安全性对比与现代属性操控规范
对比维度 textContent(强烈推荐) innerHTML(审慎使用)
数据解析机制 纯文本解析,不解析任何 HTML 标签 调用 HTML 词法解析器,构建完整子 DOM 树
XSS 安全防范 绝对安全,自动转义特殊字符(如 <script>) 极具风险,拼接未经校验的用户输入将导致脚本执行
既有事件监听 仅变更文本节点,不影响容器自身监听 彻底销毁并重建所有子节点,原有子元素事件全部丢失
执行性能开销 轻量快捷,无解析损耗 消耗额外 CPU 资源进行语法树构建与解析

2. 属性与类名修改源代码

const banner = document.querySelector('#promoBanner');

// 1. 类名增删切换(推荐使用 classList API)
banner.classList.add('visible');       // 添加类
banner.classList.remove('hidden');     // 移除类
banner.classList.toggle('expanded');   // 切换状态类(有则删,无则加)
const isExpanded = banner.classList.contains('expanded'); // 状态检测

// 2. HTML 标准属性操作
banner.setAttribute('role', 'alert');
banner.setAttribute('aria-hidden', 'false');
const currentRole = banner.getAttribute('role');

// 3. 自定义数据集操作(dataset 自动映射 data-* 属性)
banner.dataset.campaignId = "2026-spring-01"; // 对应 data-campaign-id
console.log(banner.dataset.campaignId);

四、元素移除(删):安全清理与内存管理

删除 DOM 节点不仅要将元素从视觉树中剥离,还需要确保 JavaScript 运行时的垃圾回收机制(GC)能够正常回收该节点所占用的内存。

1. 常用移除方法:el.remove() 与 parent.removeChild()

在现代浏览器中,元素自身提供了 element.remove() 方法,无需显式寻找其父级节点即可直接自我销毁;如果需要支持极为陈旧的环境,可使用传统的 parent.removeChild(child)。

// 现代直接移除(自删除)
const targetCard = document.querySelector('.card[data-id="101"]');
if (targetCard) {
  targetCard.remove();
}

// 传统父节点代理移除
const parentBox = document.querySelector('#cardList');
const oldChild = parentBox.querySelector('.card.obsolete');
if (oldChild && oldChild.parentNode === parentBox) {
  parentBox.removeChild(oldChild);
}

// 极速清空容器内所有子节点(现代原生 API)
parentBox.replaceChildren(); // 相比 parentBox.innerHTML = '' 性能更高且语义明确
⚠️ 防范“脱离文档流的 DOM 内存泄漏”
当调用 element.remove() 将元素从 DOM 树中移除后,如果全局变量、对象属性或闭包中依然保留着对该 DOM 对象的引用,该节点将无法被垃圾回收器释放,形成“游离 DOM 泄漏”。在销毁节点后,务必将持有该节点引用的变量赋值为 null。

五、事件绑定与事件委托(事件处理):让页面产生交互

页面动态交互的核心在于事件监听。传统的做法是为每个目标节点单独绑定事件函数,但在复杂动态列表中,这种做法会导致庞大的内存开销和频繁的重新绑定工作。通过理解浏览器的事件流机制,我们能够以优雅的事件委托(Event Delegation)彻底解决这一问题。

1. 浏览器事件流的三阶段模型

当用户在屏幕上触发点击时,事件并非直接出现在目标元素上,而是经历三个明确阶段:

  1. 捕获阶段(Capture Phase):事件由顶层窗口(window)、文档逐步向下传递,直到目标的直接父节点。
  2. 目标阶段(Target Phase):事件抵达用户实际点击的最深层元素。
  3. 冒泡阶段(Bubble Phase):事件沿 DOM 树从目标节点原路向上逐层冒泡传递,直至 window。
JavaScript DOM操作源代码:元素增删改查与事件绑定实例
图 4:DOM 事件流三阶段模型与事件委托机制

2. 事件委托的核心思想与对比优势

利用事件冒泡机制,我们无需在每一个子元素上单独挂载监听器,而是在它们共同的父级容器上设置单个 addEventListener。当内部子元素被点击时,事件会冒泡到父级,父级通过检测 e.target 识别触发来源并执行对应业务。

// 获取列表父级容器
const todoList = document.querySelector('#todoList');

// 仅在父级绑定单次点击监听(事件委托)
todoList.addEventListener('click', (event) => {
  // 1. 判断是否点击了删除按钮(使用 closest 兼容按钮内部图标点击场景)
  const deleteBtn = event.target.closest('.btn-delete');
  if (deleteBtn && todoList.contains(deleteBtn)) {
    const li = deleteBtn.closest('.todo-item');
    const taskId = li.dataset.id;
    console.log(`执行删除任务 ID: ${taskId}`);
    li.remove(); // 移除当前行
    return;
  }

  // 2. 判断是否点击了任务完成勾选框
  const checkbox = event.target.closest('.todo-checkbox');
  if (checkbox && todoList.contains(checkbox)) {
    const li = checkbox.closest('.todo-item');
    li.classList.toggle('completed', checkbox.checked);
    console.log(`任务状态切换: ${checkbox.checked ? '已完成' : '未完成'}`);
  }
});

六、综合实战案例:待办清单交互原型

下面是一个将元素查找、动态创建、属性修改、节点删除与事件委托无缝结合的完整可运行范例。读者可以直接将其保存为单个 .html 文件在任意现代浏览器中运行验证。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>DOM 操作综合实例</title>
  <style>
    .todo-app { max-width: 420px; margin: 30px auto; font-family: sans-serif; }
    .todo-list { list-style: none; padding: 0; margin-top: 15px; }
    .todo-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid #eee; }
    .todo-item.done .text { text-decoration: line-through; color: #999; }
    .btn-del { background: #ff4d4f; color: #fff; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; }
  </style>
</head>
<body>
  <div class="todo-app">
    <h3>待办任务管理</h3>
    <div>
      <input type="text" id="taskInput" placeholder="输入待办事项..." />
      <button id="addBtn">添加任务</button>
    </div>
    <ul class="todo-list" id="taskList"></ul>
  </div>

  <script>
    // 1. 查找关键 DOM 节点
    const taskInput = document.getElementById('taskInput');
    const addBtn = document.getElementById('addBtn');
    const taskList = document.getElementById('taskList');

    let taskIdCounter = 1;

    // 2. 元素创建与新增逻辑
    function handleAddTask() {
      const taskText = taskInput.value.trim();
      if (!taskText) return;

      const li = document.createElement('li');
      li.className = 'todo-item';
      li.dataset.id = String(taskIdCounter++);

      const span = document.createElement('span');
      span.className = 'text';
      span.textContent = taskText; // 安全注入纯文本

      const delBtn = document.createElement('button');
      delBtn.className = 'btn-del';
      delBtn.textContent = '删除';

      li.append(span, delBtn);
      taskList.append(li);

      // 清空输入框并重置焦点
      taskInput.value = '';
      taskInput.focus();
    }

    // 3. 基础事件绑定:输入框回车与添加按钮点击
    addBtn.addEventListener('click', handleAddTask);
    taskInput.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') handleAddTask();
    });

    // 4. 事件委托:在父容器处理子元素点击(完成状态修改与单项删除)
    taskList.addEventListener('click', (e) => {
      // 场景 A:点击删除按钮
      const delBtn = e.target.closest('.btn-del');
      if (delBtn) {
        const li = delBtn.closest('.todo-item');
        li.remove(); // 直接删除节点
        return;
      }

      // 场景 B:点击文本切换完成状态
      const textSpan = e.target.closest('.text');
      if (textSpan) {
        const li = textSpan.closest('.todo-item');
        li.classList.toggle('done'); // 修改类名
      }
    });
  </script>
</body>
</html>

七、核心 API 汇总与最佳实践总结

为了便于在日常开发中快速回顾与选型,下方的图谱整理了原生 DOM 操作在增、删、改、查、控五个维度的核心 API 规范与性能原则。

JavaScript DOM操作源代码:元素增删改查与事件绑定实例
图 5:JavaScript DOM 核心操作方法与最佳实践速查图谱
📌 日常开发核心规范四守则
  1. 优先使用 textContent 赋值:除非必须渲染已知安全的富文本标记,否则坚决杜绝用 innerHTML 处理用户输入。
  2. 批量挂载采用 DocumentFragment:将多节点的装配过程隔离在内存中,完成后一次性注入,严格抑制不必要的浏览器重排。
  3. 动态列表统一采用事件委托:将事件集中绑定在共同父容器上,配合 e.target.closest() 进行过滤,不仅降低内存开销,更能天然支持后续动态追加的子项。
  4. 节点移除后切断 JS 引用:调用 element.remove() 后,及时清理保存在变量或闭包中的节点缓存,保障内存被及时回收。
作者最新文章
相关文章
更多