🎯 课程目标

完成本课程后,你将能够:

  • 理解DOM(文档对象模型)的概念和结构
  • 掌握选择元素的各种方法
  • 能够创建、修改和删除DOM元素
  • 理解元素属性和样式的操作方法
  • 能够动态更新页面内容

📖 DOM概述

DOM(Document Object Model,文档对象模型)是HTML和XML文档的编程接口。它将文档表示为树形结构,每个节点都是文档的一部分,通过DOM可以动态地访问和更新文档内容、结构和样式。

✨ DOM树结构

  • Document节点:整个文档的根节点
  • Element节点:HTML/XML元素
  • Text节点:元素内的文本内容
  • Attribute节点:元素的属性
  • Comment节点:HTML注释

🔍 选择元素

在进行DOM操作之前,首先需要选中目标元素。JavaScript提供了多种选择元素的方法。

方法 1:通过ID选择

// 通过ID获取单个元素(最常用) const header = document.getElementById('main-header'); console.log(header); // 返回元素或null // 注意:如果元素不存在,返回null

方法 2:通过CSS选择器选择

// querySelector:返回匹配的第一个元素 const firstButton = document.querySelector('.btn'); const navLink = document.querySelector('#nav a'); // querySelectorAll:返回所有匹配元素的NodeList const allButtons = document.querySelectorAll('.btn'); const listItems = document.querySelectorAll('ul > li'); // 可以使用任意CSS选择器 const inputs = document.querySelectorAll('input[type="text"]'); const activeItems = document.querySelectorAll('.item.active'); // NodeList转为数组 const buttonsArray = Array.from(allButtons); const buttonsArray2 = [...allButtons];

方法 3:其他选择方法

// 通过标签名选择(返回HTMLCollection) const paragraphs = document.getElementsByTagName('p'); // 通过类名选择(返回HTMLCollection) const cards = document.getElementsByClassName('card'); // 通过Name属性选择(返回NodeList) const radioButtons = document.getElementsByName('gender'); // 选择表单元素 const form = document.forms['myForm']; // 通过name选择表单 const formElements = form.elements; // 获取表单内所有元素 // 获取body元素 const body = document.body; const head = document.head; const html = document.documentElement;

🎨 遍历DOM树

除了选择特定元素,有时还需要在DOM树中遍历查找。

遍历节点关系

const item = document.querySelector('.item'); // 父节点/父元素 item.parentNode; // 父节点(可能是文档节点) item.parentElement; // 父元素(如果不是元素返回null) // 子节点/子元素 item.childNodes; // 所有子节点(NodeList) item.children; // 所有子元素(HTMLCollection) item.firstChild; // 第一个子节点 item.firstElementChild; // 第一个子元素 item.lastChild; // 最后一个子节点 item.lastElementChild; // 最后一个子元素 // 兄弟节点 item.previousSibling; // 前一个兄弟节点 item.nextSibling; // 后一个兄弟节点 item.previousElementSibling; // 前一个兄弟元素 item.nextElementSibling; // 后一个兄弟元素

✏️ 创建和修改元素

使用JavaScript动态创建、修改和删除DOM元素。

创建新元素

// 创建新元素 const newDiv = document.createElement('div'); const newLink = document.createElement('a'); const newImg = document.createElement('img'); // 设置元素内容 newDiv.textContent = '这是新创建的div'; // 纯文本(安全) newDiv.innerHTML = '<span>HTML内容</span>'; // HTML(注意XSS风险) // 创建文本节点(更安全的方式) const textNode = document.createTextNode('纯文本内容'); newDiv.appendChild(textNode); // 创建片段(批量插入时性能更好) const fragment = document.createDocumentFragment(); for (let i = 0; i < 5; i++) { const item = document.createElement('div'); item.textContent = '项' + i; fragment.appendChild(item); } document.body.appendChild(fragment);

插入元素

const parent = document.querySelector('.container'); const newItem = document.createElement('div'); newItem.textContent = '新项目'; // 插入到末尾 parent.appendChild(newItem); // 插入到开头 parent.prepend(newItem); // 插入到某个元素之前 parent.insertBefore(newItem, parent.firstChild); // 高级插入方法(更灵活) parent.append('文本节点'); // 插入到末尾 parent.prepend('文本节点'); // 插入到开头 // 在指定元素前/后插入 const reference = parent.querySelector('.reference'); reference.after(newItem); // 在reference后插入 reference.before(newItem); // 在reference前插入 // replaceWith替换元素 reference.replaceWith(newItem);

删除和替换元素

const item = document.querySelector('.item'); // 删除元素 item.remove(); // 现代方法 item.parentNode.removeChild(item); // 传统方法(兼容旧浏览器) // 清空元素内容 item.innerHTML = ''; while (item.firstChild) { item.removeChild(item.firstChild); } // 替换元素 const newItem = document.createElement('div'); newItem.textContent = '替换内容'; item.replaceWith(newItem); // 克隆元素(深克隆包含所有子节点) const clonedItem = item.cloneNode(true); // true表示深克隆 // 浅克隆(只复制自己) const shallowClone = item.cloneNode(false);

🎭 操作属性和样式

操作元素属性

const link = document.querySelector('a'); // 获取属性 console.log(link.href); // 获取href属性 console.log(link.getAttribute('href')); // 同样效果 // 设置属性 link.href = 'https://example.com'; link.setAttribute('target', '_blank'); // 移除属性 link.removeAttribute('target'); // 检查属性是否存在 if (link.hasAttribute('href')) { console.log('链接有href属性'); } // data-*自定义属性 const card = document.querySelector('.card'); card.dataset.id = '123'; // 设置 data-id="123" card.dataset.userName = '张三'; // 设置 data-user-name="张三" console.log(card.dataset.id); // 读取 console.log(card.dataset.userName); // 读取(自动转为驼峰命名) // 类名操作(推荐方式) const box = document.querySelector('.box'); box.classList.add('active'); // 添加类 box.classList.remove('hidden'); // 移除类 box.classList.toggle('selected'); // 切换类(有则移除,无则添加) box.classList.contains('active'); // 检查是否包含类 // 直接操作className(不推荐) box.className = 'new-class";

操作内联样式

const box = document.querySelector('.box'); // 通过style属性操作内联样式 box.style.color = 'red'; box.style.backgroundColor = '#f0f0f0'; box.style.fontSize = '16px'; box.style.cssText = 'color: red; background: blue;'; // 批量设置 // 获取计算后的样式(只读) const computedStyle = window.getComputedStyle(box); console.log(computedStyle.color); // rgb(255, 0, 0) console.log(computedStyle.width); // "100px" // 驼峰命名 vs 连字符命名 box.style.marginTop = '20px'; // 驼峰 box.style['margin-top'] = '20px'; // 字符串(连字符)

📝 实战:动态待办列表

让我们综合运用DOM操作知识,创建一个动态的待办列表应用。

<!-- HTML结构 --> <div class="todo-app"> <h2>待办事项</h2> <div class="input-group"> <input type="text" id="todoInput" placeholder="添加新任务..."> <button id="addBtn">添加</button> </div> <ul id="todoList"></ul> </div> <script> const todoInput = document.getElementById('todoInput'); const addBtn = document.getElementById('addBtn'); const todoList = document.getElementById('todoList'); // 添加待办事项 function addTodo() { const text = todoInput.value.trim(); if (!text) return; // 创建列表项 const li = document.createElement('li'); // 创建任务文本 const span = document.createElement('span'); span.textContent = text; // 创建删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.className = 'delete-btn'; // 添加点击切换完成状态 span.addEventListener('click', () => { li.classList.toggle('completed'); }); // 删除待办事项 deleteBtn.addEventListener('click', () => { li.remove(); }); // 组装元素 li.appendChild(span); li.appendChild(deleteBtn); todoList.appendChild(li); // 清空输入框 todoInput.value = ''; } // 绑定事件 addBtn.addEventListener('click', addTodo); todoInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') addTodo(); }); </script> <style> .todo-app { max-width: 400px; margin: 20px auto; } .todo-app li { display: flex; justify-content: space-between; padding: 10px; border-bottom: 1px solid #eee; } .todo-app li.completed span { text-decoration: line-through; color: #999; } .delete-btn { background: #ff4444; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; } </style>

⚠️ 常见问题

问题原因分析解决方案
获取元素返回null元素不存在或选择器错误检查元素是否存在,确认选择器正确
innerHTML导致XSS直接插入用户输入的HTML使用textContent或对输入进行转义
修改样式不生效优先级问题或选择器 specificity使用更具体的选择器或important
动态添加的元素无法响应事件事件绑定在静态元素上使用事件委托或重新绑定
HTMLCollection和NodeList区别前者实时更新,后者静态根据需求选择,必要时转为数组

✅ 课后练习

练习要求:请独立完成以下练习任务:

  1. 练习 1:创建一个图片画廊,点击图片可以放大显示
  2. 练习 2:实现一个表格,支持按列排序功能
  3. 选做练习:创建一个动态树形菜单,支持展开和收起