简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français

站内搜索

搜索

活动公告

11-02 12:46
10-23 09:32
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,将及时处理!
10-23 09:31
10-23 09:28
通知:签到时间调整为每日4:00(东八区)
10-23 09:26

HTML DOM Web API使用实战指南从新手到专家全面掌握文档对象模型操作网页元素处理用户事件实现数据交互构建现代化响应式网页应用

3万

主题

317

科技点

3万

积分

大区版主

木柜子打湿

积分
31893

财Doro三倍冰淇淋无人之境【一阶】立华奏小樱(小丑装)⑨的冰沙以外的星空【二阶】

发表于 2025-10-4 17:30:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

文档对象模型(DOM)是Web开发的核心技术之一,它允许开发者通过JavaScript与HTML页面进行交互。DOM将HTML文档表示为一个树形结构,使开发者能够动态地访问和更新文档的内容、结构和样式。本指南将带您从DOM的基础概念开始,逐步深入到高级应用,最终能够熟练运用DOM API构建现代化、响应式的网页应用。无论您是刚入门的前端开发者,还是希望提升DOM操作技能的中级开发者,本指南都将为您提供全面而实用的知识。

DOM基础概念

什么是DOM

DOM(Document Object Model,文档对象模型)是HTML和XML文档的编程接口。它将文档表示为一个由节点构成的树形结构,每个节点代表文档中的一个部分(如元素、属性、文本等)。通过DOM,开发者可以使用JavaScript等编程语言来访问和修改文档的任何方面。

DOM树结构

HTML文档被浏览器解析后会生成一个DOM树,这个树由不同类型的节点组成:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>示例页面</title>
  5. </head>
  6. <body>
  7.     <h1>欢迎</h1>
  8.     <p>这是一个段落。</p>
  9. </body>
  10. </html>
复制代码

上述HTML代码对应的DOM树结构如下:
  1. Document
  2.     └── html
  3.         ├── head
  4.         │   └── title
  5.         │       └── "示例页面"
  6.         └── body
  7.             ├── h1
  8.             │   └── "欢迎"
  9.             └── p
  10.                 └── "这是一个段落。"
复制代码

节点类型

DOM中有多种类型的节点,常见的包括:

1. 元素节点(Element Node):表示HTML元素,如<div>、<p>等。
2. 文本节点(Text Node):表示元素中的文本内容。
3. 属性节点(Attribute Node):表示元素的属性。
4. 文档节点(Document Node):表示整个文档。
5. 注释节点(Comment Node):表示HTML注释。

节点关系

DOM树中的节点之间存在以下关系:

• 父节点(Parent):直接包含当前节点的节点。
• 子节点(Child):被当前节点直接包含的节点。
• 兄弟节点(Sibling):具有相同父节点的节点。
• 祖先节点(Ancestor):包含当前节点的所有节点(包括父节点)。
• 后代节点(Descendant):被当前节点包含的所有节点(包括子节点)。

选择和操作元素

基本选择方法

DOM提供了多种方法来选择HTML元素,最常用的包括:

通过元素的ID选择单个元素:
  1. // 获取ID为"myElement"的元素
  2. const element = document.getElementById('myElement');
  3. // 修改元素内容
  4. element.textContent = '新内容';
复制代码

通过类名选择多个元素,返回HTMLCollection:
  1. // 获取所有类名为"item"的元素
  2. const items = document.getElementsByClassName('item');
  3. // 遍历并修改每个元素
  4. for (let i = 0; i < items.length; i++) {
  5.     items[i].style.color = 'blue';
  6. }
复制代码

通过标签名选择多个元素,返回HTMLCollection:
  1. // 获取所有段落元素
  2. const paragraphs = document.getElementsByTagName('p');
  3. // 为每个段落添加类
  4. for (let i = 0; i < paragraphs.length; i++) {
  5.     paragraphs[i].classList.add('paragraph-style');
  6. }
复制代码

使用CSS选择器语法选择元素,querySelector()返回第一个匹配的元素,querySelectorAll()返回所有匹配的元素的NodeList:
  1. // 选择第一个类为"container"的div元素
  2. const container = document.querySelector('div.container');
  3. // 选择所有类为"button"的元素
  4. const buttons = document.querySelectorAll('.button');
  5. // 遍历NodeList
  6. buttons.forEach(button => {
  7.     button.addEventListener('click', function() {
  8.         alert('按钮被点击了!');
  9.     });
  10. });
复制代码

元素属性操作

使用getAttribute()和setAttribute()方法操作元素属性:
  1. // 获取元素
  2. const link = document.querySelector('a');
  3. // 获取href属性
  4. const href = link.getAttribute('href');
  5. console.log(href);
  6. // 设置新属性
  7. link.setAttribute('target', '_blank');
  8. // 检查属性是否存在
  9. const hasTarget = link.hasAttribute('target');
  10. console.log(hasTarget); // true
  11. // 移除属性
  12. link.removeAttribute('target');
复制代码

许多HTML属性可以直接通过JavaScript属性访问:
  1. // 获取元素
  2. const input = document.querySelector('input');
  3. // 获取和设置value属性
  4. console.log(input.value);
  5. input.value = '新值';
  6. // 获取和设置disabled属性
  7. console.log(input.disabled);
  8. input.disabled = true;
复制代码

HTML5允许使用自定义数据属性存储数据,通过dataset属性访问:
  1. <div id="user" data-id="123" data-name="张三" data-role="admin"></div>
复制代码
  1. // 获取元素
  2. const user = document.getElementById('user');
  3. // 访问数据属性
  4. console.log(user.dataset.id); // "123"
  5. console.log(user.dataset.name); // "张三"
  6. console.log(user.dataset.role); // "admin"
  7. // 修改数据属性
  8. user.dataset.role = 'superadmin';
  9. // 添加新的数据属性
  10. user.dataset.email = 'zhangsan@example.com';
复制代码

元素内容操作

innerHTML获取或设置元素的HTML内容,textContent获取或设置元素的文本内容:
  1. <div id="content">
  2.     <p>原始段落</p>
  3. </div>
复制代码
  1. // 获取元素
  2. const content = document.getElementById('content');
  3. // 获取HTML内容
  4. console.log(content.innerHTML); // "<p>原始段落</p>"
  5. // 获取文本内容
  6. console.log(content.textContent); // "原始段落"
  7. // 设置HTML内容
  8. content.innerHTML = '<h2>新标题</h2><p>新段落</p>';
  9. // 设置文本内容(会解析HTML标签为文本)
  10. content.textContent = '<h2>新标题</h2>'; // 显示为文本而非HTML
复制代码

使用DOM方法创建新元素并插入到文档中:
  1. // 创建新元素
  2. const newParagraph = document.createElement('p');
  3. newParagraph.textContent = '这是一个新段落';
  4. // 获取父元素
  5. const container = document.getElementById('container');
  6. // 将新元素添加到父元素的末尾
  7. container.appendChild(newParagraph);
  8. // 在特定位置插入元素
  9. const firstChild = container.firstChild;
  10. container.insertBefore(newParagraph, firstChild);
  11. // 替换现有元素
  12. const oldElement = document.getElementById('old-element');
  13. container.replaceChild(newParagraph, oldElement);
  14. // 移除元素
  15. container.removeChild(newParagraph);
复制代码

事件处理

事件类型

Web中常见的事件类型包括:

1. 鼠标事件:click, dblclick, mousedown, mouseup, mousemove, mouseover, mouseout
2. 键盘事件:keydown, keyup, keypress
3. 表单事件:submit, change, focus, blur
4. 文档/窗口事件:load, resize, scroll, unload

事件监听

使用addEventListener()方法为元素添加事件监听器:
  1. // 获取按钮元素
  2. const button = document.getElementById('myButton');
  3. // 添加点击事件监听器
  4. button.addEventListener('click', function() {
  5.     alert('按钮被点击了!');
  6. });
  7. // 使用箭头函数
  8. button.addEventListener('click', () => {
  9.     console.log('箭头函数处理点击事件');
  10. });
  11. // 添加多个事件监听器
  12. button.addEventListener('click', function() {
  13.     console.log('第一个点击处理器');
  14. });
  15. button.addEventListener('click', function() {
  16.     console.log('第二个点击处理器');
  17. });
复制代码

移除事件监听器

使用removeEventListener()方法移除事件监听器:
  1. // 定义处理函数
  2. function handleClick() {
  3.     console.log('点击事件处理');
  4. }
  5. // 添加事件监听器
  6. button.addEventListener('click', handleClick);
  7. // 移除事件监听器
  8. button.removeEventListener('click', handleClick);
复制代码

注意:要移除事件监听器,必须使用与添加时相同的函数引用。因此,匿名函数无法被移除。

事件对象

当事件发生时,事件处理函数会接收一个事件对象作为参数,包含关于事件的详细信息:
  1. button.addEventListener('click', function(event) {
  2.     // 阻止默认行为
  3.     event.preventDefault();
  4.    
  5.     // 阻止事件冒泡
  6.     event.stopPropagation();
  7.    
  8.     // 获取事件目标
  9.     const target = event.target;
  10.     console.log('点击的元素:', target);
  11.    
  12.     // 获取鼠标位置
  13.     console.log('鼠标X坐标:', event.clientX);
  14.     console.log('鼠标Y坐标:', event.clientY);
  15.    
  16.     // 对于键盘事件
  17.     console.log('按下的键:', event.key);
  18.     console.log('键码:', event.keyCode);
  19. });
复制代码

事件委托

事件委托是一种利用事件冒泡机制的技术,通过在父元素上设置事件监听器来处理子元素的事件:
  1. <ul id="itemList">
  2.     <li data-id="1">项目1</li>
  3.     <li data-id="2">项目2</li>
  4.     <li data-id="3">项目3</li>
  5. </ul>
复制代码
  1. // 在父元素上设置事件监听器
  2. const itemList = document.getElementById('itemList');
  3. itemList.addEventListener('click', function(event) {
  4.     // 检查点击的是否是li元素
  5.     if (event.target.tagName === 'LI') {
  6.         // 获取数据属性
  7.         const itemId = event.target.dataset.id;
  8.         console.log('点击了项目:', itemId);
  9.         
  10.         // 添加高亮效果
  11.         // 先移除所有项目的高亮
  12.         document.querySelectorAll('#itemList li').forEach(item => {
  13.             item.classList.remove('highlight');
  14.         });
  15.         
  16.         // 为当前项目添加高亮
  17.         event.target.classList.add('highlight');
  18.     }
  19. });
复制代码

事件委托的优点是:

• 减少事件监听器的数量,提高性能
• 可以处理动态添加的元素的事件
• 简化代码管理

样式操作

直接修改样式

通过元素的style属性可以直接修改CSS样式:
  1. // 获取元素
  2. const box = document.getElementById('box');
  3. // 修改单个样式
  4. box.style.backgroundColor = 'blue';
  5. box.style.width = '200px';
  6. box.style.height = '100px';
  7. // 注意:CSS属性名使用驼峰命名法
  8. // font-size → fontSize
  9. // margin-top → marginTop
复制代码

类名操作

使用classListAPI操作元素的类名:
  1. // 获取元素
  2. const element = document.getElementById('myElement');
  3. // 添加类
  4. element.classList.add('active');
  5. element.classList.add('highlight', 'large');
  6. // 移除类
  7. element.classList.remove('active');
  8. // 切换类(如果存在则移除,不存在则添加)
  9. element.classList.toggle('visible');
  10. // 检查是否包含某个类
  11. const hasClass = element.classList.contains('highlight');
  12. console.log(hasClass); // true 或 false
  13. // 替换类
  14. element.classList.replace('old-class', 'new-class');
复制代码

计算样式

使用getComputedStyle()获取元素的计算样式:
  1. // 获取元素
  2. const element = document.getElementById('myElement');
  3. // 获取计算样式
  4. const styles = window.getComputedStyle(element);
  5. // 获取特定样式属性
  6. const backgroundColor = styles.backgroundColor;
  7. const width = styles.width;
  8. const fontSize = styles.fontSize;
  9. console.log('背景颜色:', backgroundColor);
  10. console.log('宽度:', width);
  11. console.log('字体大小:', fontSize);
复制代码

响应式样式处理

根据窗口大小或其他条件动态调整样式:
  1. // 响应窗口大小变化
  2. window.addEventListener('resize', function() {
  3.     const width = window.innerWidth;
  4.     const element = document.getElementById('responsiveElement');
  5.    
  6.     if (width < 600) {
  7.         element.style.fontSize = '14px';
  8.         element.classList.add('mobile-layout');
  9.     } else if (width < 900) {
  10.         element.style.fontSize = '16px';
  11.         element.classList.remove('mobile-layout');
  12.         element.classList.add('tablet-layout');
  13.     } else {
  14.         element.style.fontSize = '18px';
  15.         element.classList.remove('mobile-layout', 'tablet-layout');
  16.         element.classList.add('desktop-layout');
  17.     }
  18. });
  19. // 初始调用一次以设置初始状态
  20. window.dispatchEvent(new Event('resize'));
复制代码

数据交互

表单数据处理
  1. // 获取表单元素
  2. const form = document.getElementById('myForm');
  3. // 方法1:通过FormData API
  4. function getFormData1() {
  5.     const formData = new FormData(form);
  6.    
  7.     // 转换为普通对象
  8.     const data = {};
  9.     for (let [key, value] of formData.entries()) {
  10.         data[key] = value;
  11.     }
  12.    
  13.     console.log(data);
  14.     return data;
  15. }
  16. // 方法2:手动获取表单字段值
  17. function getFormData2() {
  18.     const data = {
  19.         username: form.querySelector('#username').value,
  20.         email: form.querySelector('#email').value,
  21.         password: form.querySelector('#password').value,
  22.         newsletter: form.querySelector('#newsletter').checked
  23.     };
  24.    
  25.     console.log(data);
  26.     return data;
  27. }
  28. // 表单提交事件
  29. form.addEventListener('submit', function(event) {
  30.     // 阻止表单默认提交行为
  31.     event.preventDefault();
  32.    
  33.     // 获取表单数据
  34.     const formData = getFormData1();
  35.    
  36.     // 发送数据到服务器
  37.     sendDataToServer(formData);
  38. });
复制代码
  1. // 表单验证函数
  2. function validateForm() {
  3.     const form = document.getElementById('myForm');
  4.     const username = form.querySelector('#username').value;
  5.     const email = form.querySelector('#email').value;
  6.     const password = form.querySelector('#password').value;
  7.    
  8.     let isValid = true;
  9.     let errorMessage = '';
  10.    
  11.     // 清除之前的错误信息
  12.     document.querySelectorAll('.error-message').forEach(el => el.remove());
  13.    
  14.     // 验证用户名
  15.     if (username.length < 3) {
  16.         showError('username', '用户名至少需要3个字符');
  17.         isValid = false;
  18.     }
  19.    
  20.     // 验证邮箱
  21.     const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  22.     if (!emailRegex.test(email)) {
  23.         showError('email', '请输入有效的邮箱地址');
  24.         isValid = false;
  25.     }
  26.    
  27.     // 验证密码
  28.     if (password.length < 6) {
  29.         showError('password', '密码至少需要6个字符');
  30.         isValid = false;
  31.     }
  32.    
  33.     return isValid;
  34. }
  35. // 显示错误信息
  36. function showError(fieldName, message) {
  37.     const field = document.getElementById(fieldName);
  38.     const errorElement = document.createElement('div');
  39.     errorElement.className = 'error-message';
  40.     errorElement.textContent = message;
  41.     errorElement.style.color = 'red';
  42.     errorElement.style.fontSize = '12px';
  43.    
  44.     field.parentNode.insertBefore(errorElement, field.nextSibling);
  45. }
  46. // 实时验证
  47. document.getElementById('username').addEventListener('input', function() {
  48.     if (this.value.length < 3) {
  49.         showError('username', '用户名至少需要3个字符');
  50.     } else {
  51.         // 清除错误信息
  52.         const errorElement = this.parentNode.querySelector('.error-message');
  53.         if (errorElement) {
  54.             errorElement.remove();
  55.         }
  56.     }
  57. });
  58. // 表单提交时验证
  59. document.getElementById('myForm').addEventListener('submit', function(event) {
  60.     if (!validateForm()) {
  61.         event.preventDefault();
  62.     }
  63. });
复制代码

与服务器交互
  1. // GET请求示例
  2. function fetchData(url) {
  3.     fetch(url)
  4.         .then(response => {
  5.             if (!response.ok) {
  6.                 throw new Error('网络响应不正常');
  7.             }
  8.             return response.json();
  9.         })
  10.         .then(data => {
  11.             console.log('获取的数据:', data);
  12.             renderData(data);
  13.         })
  14.         .catch(error => {
  15.             console.error('获取数据时出错:', error);
  16.             showError('获取数据失败: ' + error.message);
  17.         });
  18. }
  19. // POST请求示例
  20. function postData(url, data) {
  21.     fetch(url, {
  22.         method: 'POST',
  23.         headers: {
  24.             'Content-Type': 'application/json'
  25.         },
  26.         body: JSON.stringify(data)
  27.     })
  28.     .then(response => {
  29.         if (!response.ok) {
  30.             throw new Error('网络响应不正常');
  31.         }
  32.         return response.json();
  33.     })
  34.     .then(data => {
  35.         console.log('服务器响应:', data);
  36.         showSuccess('数据提交成功');
  37.     })
  38.     .catch(error => {
  39.         console.error('提交数据时出错:', error);
  40.         showError('提交数据失败: ' + error.message);
  41.     });
  42. }
  43. // 使用示例
  44. fetchData('https://api.example.com/data');
  45. const formData = {
  46.     username: 'user123',
  47.     email: 'user@example.com'
  48. };
  49. postData('https://api.example.com/submit', formData);
复制代码
  1. // 渲染获取的数据
  2. function renderData(data) {
  3.     const container = document.getElementById('data-container');
  4.    
  5.     // 清空容器
  6.     container.innerHTML = '';
  7.    
  8.     // 检查数据是否为数组
  9.     if (Array.isArray(data)) {
  10.         // 如果是数组,创建列表
  11.         const list = document.createElement('ul');
  12.         
  13.         data.forEach(item => {
  14.             const listItem = document.createElement('li');
  15.             listItem.textContent = item.name || item.title || JSON.stringify(item);
  16.             list.appendChild(listItem);
  17.         });
  18.         
  19.         container.appendChild(list);
  20.     } else if (typeof data === 'object') {
  21.         // 如果是对象,创建表格
  22.         const table = document.createElement('table');
  23.         table.style.width = '100%';
  24.         table.style.borderCollapse = 'collapse';
  25.         
  26.         // 创建表头
  27.         const thead = document.createElement('thead');
  28.         const headerRow = document.createElement('tr');
  29.         
  30.         Object.keys(data).forEach(key => {
  31.             const th = document.createElement('th');
  32.             th.textContent = key;
  33.             th.style.border = '1px solid #ddd';
  34.             th.style.padding = '8px';
  35.             headerRow.appendChild(th);
  36.         });
  37.         
  38.         thead.appendChild(headerRow);
  39.         table.appendChild(thead);
  40.         
  41.         // 创建表体
  42.         const tbody = document.createElement('tbody');
  43.         const row = document.createElement('tr');
  44.         
  45.         Object.values(data).forEach(value => {
  46.             const td = document.createElement('td');
  47.             td.textContent = value;
  48.             td.style.border = '1px solid #ddd';
  49.             td.style.padding = '8px';
  50.             row.appendChild(td);
  51.         });
  52.         
  53.         tbody.appendChild(row);
  54.         table.appendChild(tbody);
  55.         
  56.         container.appendChild(table);
  57.     } else {
  58.         // 其他类型,直接显示
  59.         const content = document.createElement('p');
  60.         content.textContent = String(data);
  61.         container.appendChild(content);
  62.     }
  63. }
复制代码

动态内容加载
  1. // 加载更多内容示例
  2. let page = 1;
  3. const itemsPerPage = 10;
  4. let isLoading = false;
  5. // 加载内容函数
  6. function loadContent() {
  7.     if (isLoading) return;
  8.    
  9.     isLoading = true;
  10.     const loadingIndicator = document.getElementById('loading');
  11.     loadingIndicator.style.display = 'block';
  12.    
  13.     fetch(`https://api.example.com/items?page=${page}&limit=${itemsPerPage}`)
  14.         .then(response => response.json())
  15.         .then(data => {
  16.             if (data.items && data.items.length > 0) {
  17.                 renderItems(data.items);
  18.                 page++;
  19.             } else {
  20.                 // 没有更多数据
  21.                 const endMessage = document.createElement('p');
  22.                 endMessage.textContent = '没有更多内容了';
  23.                 endMessage.style.textAlign = 'center';
  24.                 endMessage.style.margin = '20px 0';
  25.                 document.getElementById('content-container').appendChild(endMessage);
  26.                
  27.                 // 移除滚动事件监听器
  28.                 window.removeEventListener('scroll', handleScroll);
  29.             }
  30.         })
  31.         .catch(error => {
  32.             console.error('加载内容失败:', error);
  33.             showError('加载内容失败: ' + error.message);
  34.         })
  35.         .finally(() => {
  36.             isLoading = false;
  37.             loadingIndicator.style.display = 'none';
  38.         });
  39. }
  40. // 渲染项目
  41. function renderItems(items) {
  42.     const container = document.getElementById('content-container');
  43.    
  44.     items.forEach(item => {
  45.         const itemElement = document.createElement('div');
  46.         itemElement.className = 'item';
  47.         itemElement.style.padding = '15px';
  48.         itemElement.style.marginBottom = '10px';
  49.         itemElement.style.border = '1px solid #eee';
  50.         itemElement.style.borderRadius = '4px';
  51.         
  52.         const title = document.createElement('h3');
  53.         title.textContent = item.title;
  54.         
  55.         const description = document.createElement('p');
  56.         description.textContent = item.description;
  57.         
  58.         itemElement.appendChild(title);
  59.         itemElement.appendChild(description);
  60.         container.appendChild(itemElement);
  61.     });
  62. }
  63. // 滚动事件处理
  64. function handleScroll() {
  65.     const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  66.     const windowHeight = window.innerHeight;
  67.     const documentHeight = document.documentElement.scrollHeight;
  68.    
  69.     // 当滚动到接近底部时加载更多内容
  70.     if (scrollTop + windowHeight >= documentHeight - 100) {
  71.         loadContent();
  72.     }
  73. }
  74. // 初始加载
  75. loadContent();
  76. // 添加滚动事件监听器
  77. window.addEventListener('scroll', handleScroll);
  78. // 刷新按钮
  79. document.getElementById('refresh-button').addEventListener('click', function() {
  80.     page = 1;
  81.     document.getElementById('content-container').innerHTML = '';
  82.     loadContent();
  83. });
复制代码

动态内容创建

创建元素和属性
  1. // 创建复杂元素结构
  2. function createProductCard(product) {
  3.     // 创建主容器
  4.     const card = document.createElement('div');
  5.     card.className = 'product-card';
  6.     card.dataset.productId = product.id;
  7.    
  8.     // 创建图片容器
  9.     const imageContainer = document.createElement('div');
  10.     imageContainer.className = 'product-image';
  11.    
  12.     // 创建图片
  13.     const image = document.createElement('img');
  14.     image.src = product.imageUrl;
  15.     image.alt = product.name;
  16.     image.loading = 'lazy'; // 懒加载
  17.    
  18.     imageContainer.appendChild(image);
  19.     card.appendChild(imageContainer);
  20.    
  21.     // 创建信息容器
  22.     const infoContainer = document.createElement('div');
  23.     infoContainer.className = 'product-info';
  24.    
  25.     // 创建标题
  26.     const title = document.createElement('h3');
  27.     title.className = 'product-title';
  28.     title.textContent = product.name;
  29.    
  30.     // 创建描述
  31.     const description = document.createElement('p');
  32.     description.className = 'product-description';
  33.     description.textContent = product.description;
  34.    
  35.     // 创建价格
  36.     const price = document.createElement('div');
  37.     price.className = 'product-price';
  38.     price.textContent = `$${product.price.toFixed(2)}`;
  39.    
  40.     // 创建按钮容器
  41.     const buttonContainer = document.createElement('div');
  42.     buttonContainer.className = 'product-actions';
  43.    
  44.     // 创建添加到购物车按钮
  45.     const addToCartButton = document.createElement('button');
  46.     addToCartButton.className = 'btn btn-primary add-to-cart';
  47.     addToCartButton.textContent = '添加到购物车';
  48.     addToCartButton.dataset.productId = product.id;
  49.    
  50.     // 创建查看详情按钮
  51.     const viewDetailsButton = document.createElement('button');
  52.     viewDetailsButton.className = 'btn btn-secondary view-details';
  53.     viewDetailsButton.textContent = '查看详情';
  54.     viewDetailsButton.dataset.productId = product.id;
  55.    
  56.     buttonContainer.appendChild(addToCartButton);
  57.     buttonContainer.appendChild(viewDetailsButton);
  58.    
  59.     // 组装信息容器
  60.     infoContainer.appendChild(title);
  61.     infoContainer.appendChild(description);
  62.     infoContainer.appendChild(price);
  63.     infoContainer.appendChild(buttonContainer);
  64.    
  65.     // 组装卡片
  66.     card.appendChild(infoContainer);
  67.    
  68.     return card;
  69. }
  70. // 使用示例
  71. const product = {
  72.     id: '123',
  73.     name: '高级无线耳机',
  74.     description: '降噪功能,30小时续航,高音质',
  75.     price: 199.99,
  76.     imageUrl: 'https://example.com/images/headphones.jpg'
  77. };
  78. const productCard = createProductCard(product);
  79. document.getElementById('products-container').appendChild(productCard);
复制代码

文档片段优化

使用文档片段(DocumentFragment)可以提高大量元素插入的性能:
  1. // 不使用文档片段(性能较差)
  2. function renderItemsWithoutFragment(items) {
  3.     const container = document.getElementById('items-container');
  4.    
  5.     items.forEach(item => {
  6.         const itemElement = document.createElement('div');
  7.         itemElement.className = 'item';
  8.         itemElement.textContent = item.name;
  9.         
  10.         // 每次appendChild都会触发重排
  11.         container.appendChild(itemElement);
  12.     });
  13. }
  14. // 使用文档片段(性能更好)
  15. function renderItemsWithFragment(items) {
  16.     const container = document.getElementById('items-container');
  17.     const fragment = document.createDocumentFragment();
  18.    
  19.     items.forEach(item => {
  20.         const itemElement = document.createElement('div');
  21.         itemElement.className = 'item';
  22.         itemElement.textContent = item.name;
  23.         
  24.         // 先添加到文档片段中,不会触发重排
  25.         fragment.appendChild(itemElement);
  26.     });
  27.    
  28.     // 一次性将文档片段添加到DOM,只触发一次重排
  29.     container.appendChild(fragment);
  30. }
  31. // 性能对比示例
  32. function testPerformance() {
  33.     const items = Array.from({length: 1000}, (_, i) => ({name: `项目 ${i+1}`}));
  34.    
  35.     console.time('不使用文档片段');
  36.     renderItemsWithoutFragment(items);
  37.     console.timeEnd('不使用文档片段');
  38.    
  39.     // 清空容器
  40.     document.getElementById('items-container').innerHTML = '';
  41.    
  42.     console.time('使用文档片段');
  43.     renderItemsWithFragment(items);
  44.     console.timeEnd('使用文档片段');
  45. }
  46. // 运行性能测试
  47. testPerformance();
复制代码

模板元素

使用HTML模板元素(<template>)可以定义可重用的HTML结构:
  1. <!-- 在HTML中定义模板 -->
  2. <template id="user-card-template">
  3.     <div class="user-card">
  4.         <div class="user-avatar">
  5.             <img src="" alt="用户头像">
  6.         </div>
  7.         <div class="user-info">
  8.             <h3 class="user-name"></h3>
  9.             <p class="user-email"></p>
  10.             <div class="user-actions">
  11.                 <button class="btn btn-edit">编辑</button>
  12.                 <button class="btn btn-delete">删除</button>
  13.             </div>
  14.         </div>
  15.     </div>
  16. </template>
复制代码
  1. // 使用模板创建用户卡片
  2. function createUserCard(user) {
  3.     // 获取模板
  4.     const template = document.getElementById('user-card-template');
  5.    
  6.     // 克隆模板内容
  7.     const clone = document.importNode(template.content, true);
  8.    
  9.     // 填充数据
  10.     clone.querySelector('.user-name').textContent = user.name;
  11.     clone.querySelector('.user-email').textContent = user.email;
  12.     clone.querySelector('.user-avatar img').src = user.avatarUrl;
  13.     clone.querySelector('.user-avatar img').alt = user.name;
  14.    
  15.     // 添加事件监听器
  16.     const editButton = clone.querySelector('.btn-edit');
  17.     const deleteButton = clone.querySelector('.btn-delete');
  18.    
  19.     editButton.addEventListener('click', () => editUser(user.id));
  20.     deleteButton.addEventListener('click', () => deleteUser(user.id));
  21.    
  22.     // 创建一个容器来容纳克隆的内容
  23.     const container = document.createElement('div');
  24.     container.appendChild(clone);
  25.    
  26.     return container;
  27. }
  28. // 使用示例
  29. const user = {
  30.     id: '456',
  31.     name: '李四',
  32.     email: 'lisi@example.com',
  33.     avatarUrl: 'https://example.com/avatars/user456.jpg'
  34. };
  35. const userCard = createUserCard(user);
  36. document.getElementById('users-container').appendChild(userCard);
  37. // 编辑用户函数
  38. function editUser(userId) {
  39.     console.log(`编辑用户: ${userId}`);
  40.     // 实现编辑逻辑...
  41. }
  42. // 删除用户函数
  43. function deleteUser(userId) {
  44.     console.log(`删除用户: ${userId}`);
  45.     // 实现删除逻辑...
  46. }
复制代码

高级DOM操作

性能优化
  1. // 不好的做法:多次修改样式,导致多次重排
  2. function badPerformanceExample() {
  3.     const element = document.getElementById('box');
  4.    
  5.     // 每次修改都会触发重排
  6.     element.style.width = '100px';
  7.     element.style.height = '100px';
  8.     element.style.backgroundColor = 'red';
  9.     element.style.border = '1px solid black';
  10. }
  11. // 好的做法:批量修改样式,减少重排次数
  12. function goodPerformanceExample() {
  13.     const element = document.getElementById('box');
  14.    
  15.     // 方法1:使用cssText一次性设置多个样式
  16.     element.style.cssText = 'width: 100px; height: 100px; background-color: red; border: 1px solid black;';
  17.    
  18.     // 方法2:添加类名(推荐)
  19.     element.className = 'box-style';
  20. }
  21. // 方法2对应的CSS
  22. /*
  23. .box-style {
  24.     width: 100px;
  25.     height: 100px;
  26.     background-color: red;
  27.     border: 1px solid black;
  28. }
  29. */
复制代码
  1. // 不好的做法:使用setInterval
  2. function badAnimation() {
  3.     const element = document.getElementById('moving-box');
  4.     let position = 0;
  5.    
  6.     const interval = setInterval(() => {
  7.         position += 5;
  8.         element.style.left = position + 'px';
  9.         
  10.         if (position >= 300) {
  11.             clearInterval(interval);
  12.         }
  13.     }, 16); // 约60fps
  14. }
  15. // 好的做法:使用requestAnimationFrame
  16. function goodAnimation() {
  17.     const element = document.getElementById('moving-box');
  18.     let position = 0;
  19.     let animationId;
  20.    
  21.     function animate() {
  22.         position += 5;
  23.         element.style.left = position + 'px';
  24.         
  25.         if (position < 300) {
  26.             animationId = requestAnimationFrame(animate);
  27.         }
  28.     }
  29.    
  30.     // 开始动画
  31.     animationId = requestAnimationFrame(animate);
  32.    
  33.     // 返回一个取消函数
  34.     return function cancel() {
  35.         cancelAnimationFrame(animationId);
  36.     };
  37. }
  38. // 使用示例
  39. const cancelAnimation = goodAnimation();
  40. // 如果需要取消动画
  41. // cancelAnimation();
复制代码

对于大量数据的列表,使用虚拟滚动可以显著提高性能:
  1. class VirtualScroll {
  2.     constructor(container, itemHeight, totalItems, renderItem) {
  3.         this.container = container;
  4.         this.itemHeight = itemHeight;
  5.         this.totalItems = totalItems;
  6.         this.renderItem = renderItem;
  7.         this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2;
  8.         this.buffer = 5; // 上下缓冲区
  9.         this.startIndex = 0;
  10.         this.scrollTop = 0;
  11.         
  12.         // 创建内容容器
  13.         this.content = document.createElement('div');
  14.         this.content.style.position = 'relative';
  15.         this.content.style.height = `${totalItems * itemHeight}px`;
  16.         container.appendChild(this.content);
  17.         
  18.         // 创建视口容器
  19.         this.viewport = document.createElement('div');
  20.         this.viewport.style.position = 'absolute';
  21.         this.viewport.style.top = '0';
  22.         this.viewport.style.left = '0';
  23.         this.viewport.style.right = '0';
  24.         this.viewport.style.bottom = '0';
  25.         this.viewport.style.overflow = 'hidden';
  26.         this.content.appendChild(this.viewport);
  27.         
  28.         // 初始化
  29.         this.renderItems();
  30.         
  31.         // 添加滚动事件监听
  32.         container.addEventListener('scroll', this.handleScroll.bind(this));
  33.     }
  34.    
  35.     handleScroll() {
  36.         const scrollTop = this.container.scrollTop;
  37.         
  38.         // 计算起始索引
  39.         const startIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.buffer);
  40.         
  41.         // 如果索引有变化,重新渲染
  42.         if (startIndex !== this.startIndex) {
  43.             this.startIndex = startIndex;
  44.             this.renderItems();
  45.         }
  46.     }
  47.    
  48.     renderItems() {
  49.         // 清空视口
  50.         this.viewport.innerHTML = '';
  51.         
  52.         // 计算要渲染的项目范围
  53.         const endIndex = Math.min(
  54.             this.totalItems - 1,
  55.             this.startIndex + this.visibleItems + this.buffer * 2
  56.         );
  57.         
  58.         // 渲染项目
  59.         for (let i = this.startIndex; i <= endIndex; i++) {
  60.             const itemElement = this.renderItem(i);
  61.             itemElement.style.position = 'absolute';
  62.             itemElement.style.top = `${i * this.itemHeight}px`;
  63.             itemElement.style.width = '100%';
  64.             itemElement.style.height = `${this.itemHeight}px`;
  65.             this.viewport.appendChild(itemElement);
  66.         }
  67.     }
  68.    
  69.     updateItem(index, data) {
  70.         // 更新特定项目的数据
  71.         // 实现取决于具体需求
  72.     }
  73.    
  74.     destroy() {
  75.         // 清理资源
  76.         this.container.removeEventListener('scroll', this.handleScroll);
  77.         this.container.innerHTML = '';
  78.     }
  79. }
  80. // 使用示例
  81. const container = document.getElementById('virtual-scroll-container');
  82. const itemHeight = 50;
  83. const totalItems = 1000;
  84. // 渲染单个项目的函数
  85. function renderItem(index) {
  86.     const item = document.createElement('div');
  87.     item.className = 'list-item';
  88.     item.textContent = `项目 ${index + 1}`;
  89.     item.style.padding = '10px';
  90.     item.style.borderBottom = '1px solid #eee';
  91.    
  92.     // 根据索引交替背景色
  93.     if (index % 2 === 0) {
  94.         item.style.backgroundColor = '#f9f9f9';
  95.     }
  96.    
  97.     return item;
  98. }
  99. // 创建虚拟滚动实例
  100. const virtualScroll = new VirtualScroll(container, itemHeight, totalItems, renderItem);
复制代码

MutationObserver

使用MutationObserver监听DOM变化:
  1. // 创建MutationObserver实例
  2. const observer = new MutationObserver((mutations) => {
  3.     mutations.forEach((mutation) => {
  4.         console.log('Mutation类型:', mutation.type);
  5.         
  6.         if (mutation.type === 'childList') {
  7.             console.log('添加的节点:', mutation.addedNodes);
  8.             console.log('移除的节点:', mutation.removedNodes);
  9.         }
  10.         
  11.         if (mutation.type === 'attributes') {
  12.             console.log('改变的属性:', mutation.attributeName);
  13.             console.log('旧值:', mutation.oldValue);
  14.         }
  15.         
  16.         if (mutation.type === 'characterData') {
  17.             console.log('文本内容改变:', mutation.target);
  18.         }
  19.     });
  20. });
  21. // 配置观察选项
  22. const config = {
  23.     attributes: true,      // 观察属性变化
  24.     childList: true,       // 观察子节点添加/删除
  25.     subtree: true,         // 观察所有后代节点的变化
  26.     characterData: true,   // 观察文本内容变化
  27.     attributeOldValue: true, // 记录属性旧值
  28.     characterDataOldValue: true // 记录文本旧值
  29. };
  30. // 开始观察目标节点
  31. const targetNode = document.getElementById('observed-container');
  32. observer.observe(targetNode, config);
  33. // 测试DOM变化
  34. function testChanges() {
  35.     // 添加子节点
  36.     const newElement = document.createElement('div');
  37.     newElement.textContent = '新添加的元素';
  38.     targetNode.appendChild(newElement);
  39.    
  40.     // 修改属性
  41.     targetNode.setAttribute('data-test', 'value');
  42.    
  43.     // 修改文本内容
  44.     const textNode = document.createTextNode('修改的文本');
  45.     targetNode.appendChild(textNode);
  46.     textNode.nodeValue = '已修改的文本';
  47.    
  48.     // 移除节点
  49.     setTimeout(() => {
  50.         targetNode.removeChild(newElement);
  51.     }, 2000);
  52. }
  53. // 运行测试
  54. testChanges();
  55. // 停止观察
  56. // observer.disconnect();
复制代码

自定义元素

创建自定义HTML元素:
  1. // 定义一个自定义元素类
  2. class UserCard extends HTMLElement {
  3.     constructor() {
  4.         super();
  5.         
  6.         // 创建影子DOM
  7.         this.attachShadow({ mode: 'open' });
  8.         
  9.         // 初始化默认数据
  10.         this._user = {
  11.             name: '默认用户',
  12.             email: 'user@example.com',
  13.             avatar: 'https://example.com/default-avatar.png'
  14.         };
  15.     }
  16.    
  17.     // 观察的属性
  18.     static get observedAttributes() {
  19.         return ['name', 'email', 'avatar'];
  20.     }
  21.    
  22.     // 属性变化时的回调
  23.     attributeChangedCallback(name, oldValue, newValue) {
  24.         if (oldValue === newValue) return;
  25.         
  26.         switch(name) {
  27.             case 'name':
  28.                 this._user.name = newValue;
  29.                 break;
  30.             case 'email':
  31.                 this._user.email = newValue;
  32.                 break;
  33.             case 'avatar':
  34.                 this._user.avatar = newValue;
  35.                 break;
  36.         }
  37.         
  38.         this.render();
  39.     }
  40.    
  41.     // 连接到DOM时的回调
  42.     connectedCallback() {
  43.         this.render();
  44.     }
  45.    
  46.     // 从DOM断开时的回调
  47.     disconnectedCallback() {
  48.         // 清理资源
  49.     }
  50.    
  51.     // 渲染方法
  52.     render() {
  53.         // 定义模板
  54.         const template = document.createElement('template');
  55.         template.innerHTML = `
  56.             <style>
  57.                 .user-card {
  58.                     display: flex;
  59.                     align-items: center;
  60.                     padding: 15px;
  61.                     border-radius: 8px;
  62.                     box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  63.                     background-color: white;
  64.                     font-family: Arial, sans-serif;
  65.                     max-width: 400px;
  66.                 }
  67.                
  68.                 .user-avatar {
  69.                     width: 60px;
  70.                     height: 60px;
  71.                     border-radius: 50%;
  72.                     margin-right: 15px;
  73.                     object-fit: cover;
  74.                 }
  75.                
  76.                 .user-info {
  77.                     flex: 1;
  78.                 }
  79.                
  80.                 .user-name {
  81.                     margin: 0 0 5px 0;
  82.                     font-size: 18px;
  83.                     color: #333;
  84.                 }
  85.                
  86.                 .user-email {
  87.                     margin: 0;
  88.                     font-size: 14px;
  89.                     color: #666;
  90.                 }
  91.             </style>
  92.             
  93.             <div class="user-card">
  94.                 <img class="user-avatar" src="${this._user.avatar}" alt="${this._user.name}">
  95.                 <div class="user-info">
  96.                     <h3 class="user-name">${this._user.name}</h3>
  97.                     <p class="user-email">${this._user.email}</p>
  98.                 </div>
  99.             </div>
  100.         `;
  101.         
  102.         // 清空影子DOM并添加模板内容
  103.         this.shadowRoot.innerHTML = '';
  104.         this.shadowRoot.appendChild(template.content.cloneNode(true));
  105.     }
  106.    
  107.     // 设置用户数据的方法
  108.     setUser(user) {
  109.         this._user = { ...this._user, ...user };
  110.         
  111.         // 更新属性
  112.         if (user.name) this.setAttribute('name', user.name);
  113.         if (user.email) this.setAttribute('email', user.email);
  114.         if (user.avatar) this.setAttribute('avatar', user.avatar);
  115.         
  116.         this.render();
  117.     }
  118.    
  119.     // 获取用户数据的方法
  120.     getUser() {
  121.         return { ...this._user };
  122.     }
  123. }
  124. // 注册自定义元素
  125. customElements.define('user-card', UserCard);
  126. // 使用示例
  127. // 在HTML中:
  128. // <user-card name="张三" email="zhangsan@example.com" avatar="https://example.com/avatar1.png"></user-card>
  129. // 或通过JavaScript创建:
  130. const userCard = document.createElement('user-card');
  131. userCard.setAttribute('name', '李四');
  132. userCard.setAttribute('email', 'lisi@example.com');
  133. userCard.setAttribute('avatar', 'https://example.com/avatar2.png');
  134. document.getElementById('user-container').appendChild(userCard);
  135. // 使用setUser方法更新数据
  136. userCard.setUser({
  137.     name: '王五',
  138.     email: 'wangwu@example.com',
  139.     avatar: 'https://example.com/avatar3.png'
  140. });
  141. // 获取用户数据
  142. const userData = userCard.getUser();
  143. console.log(userData);
复制代码

实战案例:构建一个完整的响应式网页应用

项目概述

我们将构建一个任务管理应用,具有以下功能:

1. 添加、编辑、删除任务
2. 标记任务为完成/未完成
3. 按状态筛选任务(全部/进行中/已完成)
4. 本地存储任务数据
5. 响应式设计,适配不同设备

HTML结构
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>任务管理应用</title>
  7.     <link rel="stylesheet" href="styles.css">
  8. </head>
  9. <body>
  10.     <div class="app-container">
  11.         <header class="app-header">
  12.             <h1>任务管理</h1>
  13.         </header>
  14.         
  15.         <main class="app-main">
  16.             <section class="task-input-section">
  17.                 <form id="task-form" class="task-form">
  18.                     <input
  19.                         type="text"
  20.                         id="task-input"
  21.                         class="task-input"
  22.                         placeholder="添加新任务..."
  23.                         autocomplete="off"
  24.                         required
  25.                     >
  26.                     <button type="submit" class="btn btn-primary">添加</button>
  27.                 </form>
  28.             </section>
  29.             
  30.             <section class="task-filter-section">
  31.                 <div class="filter-buttons">
  32.                     <button class="filter-btn active" data-filter="all">全部</button>
  33.                     <button class="filter-btn" data-filter="active">进行中</button>
  34.                     <button class="filter-btn" data-filter="completed">已完成</button>
  35.                 </div>
  36.                 <div class="task-stats">
  37.                     <span id="task-count">0 个任务</span>
  38.                 </div>
  39.             </section>
  40.             
  41.             <section class="task-list-section">
  42.                 <ul id="task-list" class="task-list">
  43.                     <!-- 任务项将通过JavaScript动态添加 -->
  44.                 </ul>
  45.                 <div id="empty-state" class="empty-state">
  46.                     <p>没有任务,添加一个新任务开始吧!</p>
  47.                 </div>
  48.             </section>
  49.         </main>
  50.         
  51.         <footer class="app-footer">
  52.             <p>&copy; 2023 任务管理应用</p>
  53.         </footer>
  54.     </div>
  55.     <!-- 编辑任务模态框 -->
  56.     <div id="edit-modal" class="modal">
  57.         <div class="modal-content">
  58.             <span class="close-btn">&times;</span>
  59.             <h2>编辑任务</h2>
  60.             <form id="edit-form">
  61.                 <input type="hidden" id="edit-task-id">
  62.                 <input
  63.                     type="text"
  64.                     id="edit-task-input"
  65.                     class="task-input"
  66.                     placeholder="任务内容..."
  67.                     required
  68.                 >
  69.                 <div class="form-actions">
  70.                     <button type="submit" class="btn btn-primary">保存</button>
  71.                     <button type="button" class="btn btn-secondary cancel-btn">取消</button>
  72.                 </div>
  73.             </form>
  74.         </div>
  75.     </div>
  76.     <script src="app.js"></script>
  77. </body>
  78. </html>
复制代码

CSS样式
  1. /* 基础样式 */
  2. * {
  3.     margin: 0;
  4.     padding: 0;
  5.     box-sizing: border-box;
  6. }
  7. body {
  8.     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  9.     line-height: 1.6;
  10.     color: #333;
  11.     background-color: #f5f5f5;
  12. }
  13. .app-container {
  14.     max-width: 800px;
  15.     margin: 0 auto;
  16.     padding: 20px;
  17. }
  18. /* 头部样式 */
  19. .app-header {
  20.     text-align: center;
  21.     margin-bottom: 30px;
  22. }
  23. .app-header h1 {
  24.     color: #2c3e50;
  25.     font-size: 2.5rem;
  26. }
  27. /* 主要内容区域 */
  28. .app-main {
  29.     background-color: white;
  30.     border-radius: 8px;
  31.     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  32.     padding: 20px;
  33. }
  34. /* 任务输入区域 */
  35. .task-input-section {
  36.     margin-bottom: 20px;
  37. }
  38. .task-form {
  39.     display: flex;
  40. }
  41. .task-input {
  42.     flex: 1;
  43.     padding: 12px 15px;
  44.     border: 1px solid #ddd;
  45.     border-radius: 4px 0 0 4px;
  46.     font-size: 16px;
  47. }
  48. .task-input:focus {
  49.     outline: none;
  50.     border-color: #3498db;
  51. }
  52. .btn {
  53.     padding: 12px 20px;
  54.     border: none;
  55.     border-radius: 0 4px 4px 0;
  56.     cursor: pointer;
  57.     font-size: 16px;
  58.     transition: background-color 0.3s;
  59. }
  60. .btn-primary {
  61.     background-color: #3498db;
  62.     color: white;
  63. }
  64. .btn-primary:hover {
  65.     background-color: #2980b9;
  66. }
  67. .btn-secondary {
  68.     background-color: #95a5a6;
  69.     color: white;
  70. }
  71. .btn-secondary:hover {
  72.     background-color: #7f8c8d;
  73. }
  74. /* 任务筛选区域 */
  75. .task-filter-section {
  76.     display: flex;
  77.     justify-content: space-between;
  78.     align-items: center;
  79.     margin-bottom: 20px;
  80.     padding-bottom: 15px;
  81.     border-bottom: 1px solid #eee;
  82. }
  83. .filter-buttons {
  84.     display: flex;
  85.     gap: 10px;
  86. }
  87. .filter-btn {
  88.     padding: 8px 15px;
  89.     border: 1px solid #ddd;
  90.     background-color: white;
  91.     border-radius: 4px;
  92.     cursor: pointer;
  93.     transition: all 0.3s;
  94. }
  95. .filter-btn.active {
  96.     background-color: #3498db;
  97.     color: white;
  98.     border-color: #3498db;
  99. }
  100. .filter-btn:not(.active):hover {
  101.     background-color: #f5f5f5;
  102. }
  103. .task-stats {
  104.     color: #7f8c8d;
  105.     font-size: 14px;
  106. }
  107. /* 任务列表区域 */
  108. .task-list {
  109.     list-style: none;
  110. }
  111. .task-item {
  112.     display: flex;
  113.     align-items: center;
  114.     padding: 15px;
  115.     border-bottom: 1px solid #eee;
  116.     transition: background-color 0.3s;
  117. }
  118. .task-item:hover {
  119.     background-color: #f9f9f9;
  120. }
  121. .task-item:last-child {
  122.     border-bottom: none;
  123. }
  124. .task-checkbox {
  125.     margin-right: 15px;
  126.     width: 20px;
  127.     height: 20px;
  128.     cursor: pointer;
  129. }
  130. .task-text {
  131.     flex: 1;
  132.     font-size: 16px;
  133. }
  134. .task-item.completed .task-text {
  135.     text-decoration: line-through;
  136.     color: #95a5a6;
  137. }
  138. .task-actions {
  139.     display: flex;
  140.     gap: 10px;
  141. }
  142. .task-btn {
  143.     background: none;
  144.     border: none;
  145.     cursor: pointer;
  146.     color: #7f8c8d;
  147.     font-size: 16px;
  148.     transition: color 0.3s;
  149. }
  150. .task-btn:hover {
  151.     color: #3498db;
  152. }
  153. .task-btn.delete:hover {
  154.     color: #e74c3c;
  155. }
  156. .empty-state {
  157.     text-align: center;
  158.     padding: 40px 20px;
  159.     color: #95a5a6;
  160. }
  161. /* 模态框样式 */
  162. .modal {
  163.     display: none;
  164.     position: fixed;
  165.     top: 0;
  166.     left: 0;
  167.     width: 100%;
  168.     height: 100%;
  169.     background-color: rgba(0, 0, 0, 0.5);
  170.     z-index: 1000;
  171.     justify-content: center;
  172.     align-items: center;
  173. }
  174. .modal-content {
  175.     background-color: white;
  176.     padding: 30px;
  177.     border-radius: 8px;
  178.     width: 90%;
  179.     max-width: 500px;
  180.     position: relative;
  181. }
  182. .close-btn {
  183.     position: absolute;
  184.     top: 15px;
  185.     right: 15px;
  186.     font-size: 24px;
  187.     cursor: pointer;
  188.     color: #95a5a6;
  189. }
  190. .close-btn:hover {
  191.     color: #333;
  192. }
  193. #edit-form {
  194.     margin-top: 20px;
  195. }
  196. #edit-task-input {
  197.     width: 100%;
  198.     padding: 12px 15px;
  199.     border: 1px solid #ddd;
  200.     border-radius: 4px;
  201.     font-size: 16px;
  202.     margin-bottom: 20px;
  203. }
  204. .form-actions {
  205.     display: flex;
  206.     justify-content: flex-end;
  207.     gap: 10px;
  208. }
  209. .form-actions .btn {
  210.     border-radius: 4px;
  211. }
  212. /* 响应式设计 */
  213. @media (max-width: 600px) {
  214.     .app-container {
  215.         padding: 10px;
  216.     }
  217.    
  218.     .app-header h1 {
  219.         font-size: 2rem;
  220.     }
  221.    
  222.     .task-filter-section {
  223.         flex-direction: column;
  224.         align-items: flex-start;
  225.         gap: 10px;
  226.     }
  227.    
  228.     .filter-buttons {
  229.         width: 100%;
  230.         justify-content: space-between;
  231.     }
  232.    
  233.     .task-item {
  234.         padding: 12px;
  235.     }
  236.    
  237.     .modal-content {
  238.         padding: 20px;
  239.     }
  240. }
复制代码

JavaScript逻辑
  1. // 任务管理应用类
  2. class TaskManager {
  3.     constructor() {
  4.         this.tasks = this.loadTasks();
  5.         this.currentFilter = 'all';
  6.         this.nextId = this.tasks.length > 0 ? Math.max(...this.tasks.map(task => task.id)) + 1 : 1;
  7.         
  8.         // DOM元素
  9.         this.taskForm = document.getElementById('task-form');
  10.         this.taskInput = document.getElementById('task-input');
  11.         this.taskList = document.getElementById('task-list');
  12.         this.emptyState = document.getElementById('empty-state');
  13.         this.taskCount = document.getElementById('task-count');
  14.         this.filterButtons = document.querySelectorAll('.filter-btn');
  15.         
  16.         // 编辑模态框元素
  17.         this.editModal = document.getElementById('edit-modal');
  18.         this.editForm = document.getElementById('edit-form');
  19.         this.editTaskId = document.getElementById('edit-task-id');
  20.         this.editTaskInput = document.getElementById('edit-task-input');
  21.         this.closeBtn = document.querySelector('.close-btn');
  22.         this.cancelBtn = document.querySelector('.cancel-btn');
  23.         
  24.         // 初始化
  25.         this.init();
  26.     }
  27.    
  28.     // 初始化方法
  29.     init() {
  30.         // 渲染任务列表
  31.         this.renderTasks();
  32.         
  33.         // 添加表单提交事件
  34.         this.taskForm.addEventListener('submit', (e) => {
  35.             e.preventDefault();
  36.             this.addTask(this.taskInput.value.trim());
  37.             this.taskInput.value = '';
  38.             this.taskInput.focus();
  39.         });
  40.         
  41.         // 添加筛选按钮点击事件
  42.         this.filterButtons.forEach(button => {
  43.             button.addEventListener('click', () => {
  44.                 // 更新活动按钮
  45.                 this.filterButtons.forEach(btn => btn.classList.remove('active'));
  46.                 button.classList.add('active');
  47.                
  48.                 // 更新当前筛选器并重新渲染
  49.                 this.currentFilter = button.dataset.filter;
  50.                 this.renderTasks();
  51.             });
  52.         });
  53.         
  54.         // 编辑表单提交事件
  55.         this.editForm.addEventListener('submit', (e) => {
  56.             e.preventDefault();
  57.             this.saveEditTask();
  58.         });
  59.         
  60.         // 模态框关闭事件
  61.         this.closeBtn.addEventListener('click', () => this.closeEditModal());
  62.         this.cancelBtn.addEventListener('click', () => this.closeEditModal());
  63.         
  64.         // 点击模态框外部关闭
  65.         window.addEventListener('click', (e) => {
  66.             if (e.target === this.editModal) {
  67.                 this.closeEditModal();
  68.             }
  69.         });
  70.     }
  71.    
  72.     // 从本地存储加载任务
  73.     loadTasks() {
  74.         const tasksJSON = localStorage.getItem('tasks');
  75.         return tasksJSON ? JSON.parse(tasksJSON) : [];
  76.     }
  77.    
  78.     // 保存任务到本地存储
  79.     saveTasks() {
  80.         localStorage.setItem('tasks', JSON.stringify(this.tasks));
  81.     }
  82.    
  83.     // 添加新任务
  84.     addTask(text) {
  85.         if (!text) return;
  86.         
  87.         const newTask = {
  88.             id: this.nextId++,
  89.             text: text,
  90.             completed: false,
  91.             createdAt: new Date().toISOString()
  92.         };
  93.         
  94.         this.tasks.unshift(newTask);
  95.         this.saveTasks();
  96.         this.renderTasks();
  97.     }
  98.    
  99.     // 删除任务
  100.     deleteTask(id) {
  101.         this.tasks = this.tasks.filter(task => task.id !== id);
  102.         this.saveTasks();
  103.         this.renderTasks();
  104.     }
  105.    
  106.     // 切换任务完成状态
  107.     toggleTask(id) {
  108.         const task = this.tasks.find(task => task.id === id);
  109.         if (task) {
  110.             task.completed = !task.completed;
  111.             this.saveTasks();
  112.             this.renderTasks();
  113.         }
  114.     }
  115.    
  116.     // 打开编辑模态框
  117.     openEditModal(id) {
  118.         const task = this.tasks.find(task => task.id === id);
  119.         if (task) {
  120.             this.editTaskId.value = task.id;
  121.             this.editTaskInput.value = task.text;
  122.             this.editModal.style.display = 'flex';
  123.             this.editTaskInput.focus();
  124.         }
  125.     }
  126.    
  127.     // 关闭编辑模态框
  128.     closeEditModal() {
  129.         this.editModal.style.display = 'none';
  130.         this.editTaskId.value = '';
  131.         this.editTaskInput.value = '';
  132.     }
  133.    
  134.     // 保存编辑的任务
  135.     saveEditTask() {
  136.         const id = parseInt(this.editTaskId.value);
  137.         const newText = this.editTaskInput.value.trim();
  138.         
  139.         if (!newText) return;
  140.         
  141.         const task = this.tasks.find(task => task.id === id);
  142.         if (task) {
  143.             task.text = newText;
  144.             this.saveTasks();
  145.             this.renderTasks();
  146.             this.closeEditModal();
  147.         }
  148.     }
  149.    
  150.     // 获取筛选后的任务
  151.     getFilteredTasks() {
  152.         switch (this.currentFilter) {
  153.             case 'active':
  154.                 return this.tasks.filter(task => !task.completed);
  155.             case 'completed':
  156.                 return this.tasks.filter(task => task.completed);
  157.             default:
  158.                 return this.tasks;
  159.         }
  160.     }
  161.    
  162.     // 渲染任务列表
  163.     renderTasks() {
  164.         const filteredTasks = this.getFilteredTasks();
  165.         
  166.         // 清空任务列表
  167.         this.taskList.innerHTML = '';
  168.         
  169.         // 显示或隐藏空状态
  170.         if (filteredTasks.length === 0) {
  171.             this.emptyState.style.display = 'block';
  172.             this.taskList.style.display = 'none';
  173.         } else {
  174.             this.emptyState.style.display = 'none';
  175.             this.taskList.style.display = 'block';
  176.             
  177.             // 渲染任务项
  178.             filteredTasks.forEach(task => {
  179.                 const taskItem = this.createTaskElement(task);
  180.                 this.taskList.appendChild(taskItem);
  181.             });
  182.         }
  183.         
  184.         // 更新任务计数
  185.         this.updateTaskCount();
  186.     }
  187.    
  188.     // 创建任务元素
  189.     createTaskElement(task) {
  190.         const li = document.createElement('li');
  191.         li.className = `task-item ${task.completed ? 'completed' : ''}`;
  192.         li.dataset.id = task.id;
  193.         
  194.         // 复选框
  195.         const checkbox = document.createElement('input');
  196.         checkbox.type = 'checkbox';
  197.         checkbox.className = 'task-checkbox';
  198.         checkbox.checked = task.completed;
  199.         checkbox.addEventListener('change', () => this.toggleTask(task.id));
  200.         
  201.         // 任务文本
  202.         const taskText = document.createElement('span');
  203.         taskText.className = 'task-text';
  204.         taskText.textContent = task.text;
  205.         
  206.         // 操作按钮容器
  207.         const taskActions = document.createElement('div');
  208.         taskActions.className = 'task-actions';
  209.         
  210.         // 编辑按钮
  211.         const editBtn = document.createElement('button');
  212.         editBtn.className = 'task-btn edit';
  213.         editBtn.innerHTML = '✏️';
  214.         editBtn.title = '编辑任务';
  215.         editBtn.addEventListener('click', () => this.openEditModal(task.id));
  216.         
  217.         // 删除按钮
  218.         const deleteBtn = document.createElement('button');
  219.         deleteBtn.className = 'task-btn delete';
  220.         deleteBtn.innerHTML = '🗑️';
  221.         deleteBtn.title = '删除任务';
  222.         deleteBtn.addEventListener('click', () => this.deleteTask(task.id));
  223.         
  224.         // 组装任务项
  225.         taskActions.appendChild(editBtn);
  226.         taskActions.appendChild(deleteBtn);
  227.         
  228.         li.appendChild(checkbox);
  229.         li.appendChild(taskText);
  230.         li.appendChild(taskActions);
  231.         
  232.         return li;
  233.     }
  234.    
  235.     // 更新任务计数
  236.     updateTaskCount() {
  237.         const filteredTasks = this.getFilteredTasks();
  238.         const totalTasks = this.tasks.length;
  239.         const activeTasks = this.tasks.filter(task => !task.completed).length;
  240.         const completedTasks = this.tasks.filter(task => task.completed).length;
  241.         
  242.         let countText = '';
  243.         
  244.         switch (this.currentFilter) {
  245.             case 'active':
  246.                 countText = `${activeTasks} 个进行中的任务`;
  247.                 break;
  248.             case 'completed':
  249.                 countText = `${completedTasks} 个已完成的任务`;
  250.                 break;
  251.             default:
  252.                 countText = `${totalTasks} 个任务`;
  253.         }
  254.         
  255.         this.taskCount.textContent = countText;
  256.     }
  257. }
  258. // 当DOM加载完成后初始化应用
  259. document.addEventListener('DOMContentLoaded', () => {
  260.     const taskManager = new TaskManager();
  261.    
  262.     // 添加一些示例任务(仅在首次加载时)
  263.     if (taskManager.tasks.length === 0) {
  264.         taskManager.addTask('学习JavaScript DOM操作');
  265.         taskManager.addTask('构建响应式网页应用');
  266.         taskManager.addTask('完成项目文档');
  267.         
  268.         // 标记一个任务为已完成
  269.         taskManager.toggleTask(1);
  270.     }
  271. });
复制代码

功能说明

这个任务管理应用实现了以下功能:

1. 任务管理:添加新任务编辑现有任务删除任务标记任务为完成/未完成
2. 添加新任务
3. 编辑现有任务
4. 删除任务
5. 标记任务为完成/未完成
6. 任务筛选:查看所有任务只查看进行中的任务只查看已完成的任务
7. 查看所有任务
8. 只查看进行中的任务
9. 只查看已完成的任务
10. 数据持久化:使用localStorage保存任务数据页面刷新后数据不会丢失
11. 使用localStorage保存任务数据
12. 页面刷新后数据不会丢失
13. 响应式设计:适配桌面和移动设备在小屏幕上自动调整布局
14. 适配桌面和移动设备
15. 在小屏幕上自动调整布局
16. 用户体验优化:空状态提示任务计数显示模态框编辑任务平滑的过渡动画
17. 空状态提示
18. 任务计数显示
19. 模态框编辑任务
20. 平滑的过渡动画

任务管理:

• 添加新任务
• 编辑现有任务
• 删除任务
• 标记任务为完成/未完成

任务筛选:

• 查看所有任务
• 只查看进行中的任务
• 只查看已完成的任务

数据持久化:

• 使用localStorage保存任务数据
• 页面刷新后数据不会丢失

响应式设计:

• 适配桌面和移动设备
• 在小屏幕上自动调整布局

用户体验优化:

• 空状态提示
• 任务计数显示
• 模态框编辑任务
• 平滑的过渡动画

扩展功能建议

如果想要进一步增强这个应用,可以考虑添加以下功能:

1. 任务优先级:为任务设置优先级(高、中、低)按优先级排序和筛选
2. 为任务设置优先级(高、中、低)
3. 按优先级排序和筛选
4. 任务分类:创建任务分类(如工作、个人、学习等)按分类管理任务
5. 创建任务分类(如工作、个人、学习等)
6. 按分类管理任务
7. 截止日期:为任务设置截止日期显示即将到期的任务
8. 为任务设置截止日期
9. 显示即将到期的任务
10. 搜索功能:添加搜索框,按关键词搜索任务
11. 添加搜索框,按关键词搜索任务
12. 任务统计:添加图表显示任务完成情况统计每日/每周完成的任务数量
13. 添加图表显示任务完成情况
14. 统计每日/每周完成的任务数量
15. 数据导出/导入:支持将任务数据导出为JSON或CSV文件支持从文件导入任务数据
16. 支持将任务数据导出为JSON或CSV文件
17. 支持从文件导入任务数据
18. 主题切换:添加明暗主题切换功能
19. 添加明暗主题切换功能
20. 在线同步:添加用户登录功能将任务数据同步到云端
21. 添加用户登录功能
22. 将任务数据同步到云端

任务优先级:

• 为任务设置优先级(高、中、低)
• 按优先级排序和筛选

任务分类:

• 创建任务分类(如工作、个人、学习等)
• 按分类管理任务

截止日期:

• 为任务设置截止日期
• 显示即将到期的任务

搜索功能:

• 添加搜索框,按关键词搜索任务

任务统计:

• 添加图表显示任务完成情况
• 统计每日/每周完成的任务数量

数据导出/导入:

• 支持将任务数据导出为JSON或CSV文件
• 支持从文件导入任务数据

主题切换:

• 添加明暗主题切换功能

在线同步:

• 添加用户登录功能
• 将任务数据同步到云端

总结与进阶学习资源

总结

本指南全面介绍了HTML DOM Web API的使用,从基础概念到高级应用,帮助您掌握了:

1. DOM基础:理解DOM树结构、节点类型和节点关系。
2. 元素选择与操作:使用各种方法选择和操作HTML元素。
3. 事件处理:处理用户交互,实现动态响应。
4. 样式操作:通过JavaScript动态修改页面样式。
5. 数据交互:处理表单数据,与服务器进行通信。
6. 动态内容创建:使用DOM API创建和修改页面内容。
7. 高级DOM操作:性能优化、MutationObserver和自定义元素。
8. 实战应用:构建了一个完整的响应式任务管理应用。

通过学习这些内容,您已经具备了使用DOM API构建现代化、响应式网页应用的能力。记住,实践是最好的学习方式,继续尝试构建更多项目,不断提升您的技能。

进阶学习资源

1. 官方文档:MDN Web Docs - Document Object Model (DOM)W3C DOM规范
2. MDN Web Docs - Document Object Model (DOM)
3. W3C DOM规范
4. 书籍推荐:《JavaScript高级程序设计》(第4版)- Nicholas C. Zakas《DOM Enlightenment》- Cody Lindley《You Don’t Know JS Yet》- Kyle Simpson
5. 《JavaScript高级程序设计》(第4版)- Nicholas C. Zakas
6. 《DOM Enlightenment》- Cody Lindley
7. 《You Don’t Know JS Yet》- Kyle Simpson
8. 在线课程:freeCodeCamp - JavaScript Algorithms and Data StructuresUdemy - The Complete JavaScript Course 2023Coursera - HTML, CSS, and Javascript for Web Developers
9. freeCodeCamp - JavaScript Algorithms and Data Structures
10. Udemy - The Complete JavaScript Course 2023
11. Coursera - HTML, CSS, and Javascript for Web Developers
12. 实践项目:构建一个交互式待办事项应用开发一个实时天气预报应用创建一个动态数据可视化仪表板实现一个简单的在线文本编辑器
13. 构建一个交互式待办事项应用
14. 开发一个实时天气预报应用
15. 创建一个动态数据可视化仪表板
16. 实现一个简单的在线文本编辑器
17. 社区和论坛:Stack OverflowGitHubReddit - r/javascript前端开发者社区
18. Stack Overflow
19. GitHub
20. Reddit - r/javascript
21. 前端开发者社区
22. 工具和库:jQuery- 简化DOM操作的流行库React- 用于构建用户界面的JavaScript库Vue.js- 渐进式JavaScript框架Angular- 平台和框架,用于构建客户端应用
23. jQuery- 简化DOM操作的流行库
24. React- 用于构建用户界面的JavaScript库
25. Vue.js- 渐进式JavaScript框架
26. Angular- 平台和框架,用于构建客户端应用

官方文档:

• MDN Web Docs - Document Object Model (DOM)
• W3C DOM规范

书籍推荐:

• 《JavaScript高级程序设计》(第4版)- Nicholas C. Zakas
• 《DOM Enlightenment》- Cody Lindley
• 《You Don’t Know JS Yet》- Kyle Simpson

在线课程:

• freeCodeCamp - JavaScript Algorithms and Data Structures
• Udemy - The Complete JavaScript Course 2023
• Coursera - HTML, CSS, and Javascript for Web Developers

实践项目:

• 构建一个交互式待办事项应用
• 开发一个实时天气预报应用
• 创建一个动态数据可视化仪表板
• 实现一个简单的在线文本编辑器

社区和论坛:

• Stack Overflow
• GitHub
• Reddit - r/javascript
• 前端开发者社区

工具和库:

• jQuery- 简化DOM操作的流行库
• React- 用于构建用户界面的JavaScript库
• Vue.js- 渐进式JavaScript框架
• Angular- 平台和框架,用于构建客户端应用

通过不断学习和实践,您将能够更加熟练地运用DOM API,创建出更加复杂和功能丰富的Web应用。祝您在Web开发的道路上取得成功!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

频道订阅

频道订阅

加入社群

加入社群

联系我们|TG频道|RSS

Powered by Pixtech

© 2025 Pixtech Team.