引言

HTML DOM(文档对象模型)是前端开发的核心技术之一,它允许开发者通过JavaScript动态地访问和修改HTML文档的内容、结构和样式。掌握DOM操作是成为一名专业前端开发者的必备技能,它能够帮助你创建更加动态、交互性强的网页应用,提升用户体验。

本文将从基础概念讲起,逐步深入到高级技巧和最佳实践,帮助你全面掌握HTML DOM元素脚本编写。无论你是初学者还是有一定经验的开发者,都能从本文中获得实用的知识和技巧,提升你的前端开发能力。

HTML DOM基础

DOM的定义和结构

DOM(Document Object Model)是HTML和XML文档的编程接口,它将文档表示为一个树状结构,其中每个节点都是文档的一部分(如元素、属性、文本等)。通过DOM,开发者可以动态地访问和更新文档的内容、结构和样式。

<!DOCTYPE html> <html> <head> <title>DOM示例</title> </head> <body> <h1>欢迎学习DOM</h1> <p>这是一个段落。</p> </body> </html> 

在上述HTML文档中,DOM树的结构如下:

Document └── html ├── head │ └── title │ └── "DOM示例" └── body ├── h1 │ └── "欢迎学习DOM" └── p └── "这是一个段落。" 

DOM树的概念

DOM树是由节点组成的层次结构,每个节点代表文档中的一部分。主要有以下几种节点类型:

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

节点类型

在JavaScript中,每个节点都有一个nodeType属性,用于表示节点的类型:

  • Node.ELEMENT_NODE (1):元素节点
  • Node.ATTRIBUTE_NODE (2):属性节点
  • Node.TEXT_NODE (3):文本节点
  • Node.COMMENT_NODE (8):注释节点
  • Node.DOCUMENT_NODE (9):文档节点
  • Node.DOCUMENT_FRAGMENT_NODE (11):文档片段节点
// 示例:检查节点类型 const element = document.getElementById('myElement'); console.log(element.nodeType === Node.ELEMENT_NODE); // 输出: true const textNode = element.firstChild; console.log(textNode.nodeType === Node.TEXT_NODE); // 输出: true 

DOM元素的选择与访问

基本选择方法

DOM提供了多种方法来选择和访问元素,最常用的有以下几种:

  1. getElementById():通过元素的ID选择元素。
  2. getElementsByClassName():通过类名选择元素集合。
  3. getElementsByTagName():通过标签名选择元素集合。
// 通过ID选择元素 const header = document.getElementById('header'); // 通过类名选择元素集合 const items = document.getElementsByClassName('item'); // 通过标签名选择元素集合 const paragraphs = document.getElementsByTagName('p'); 

高级选择方法

现代浏览器支持更强大的选择方法:

  1. querySelector():通过CSS选择器选择第一个匹配的元素。
  2. querySelectorAll():通过CSS选择器选择所有匹配的元素集合。
// 选择第一个具有类名'active'的元素 const activeElement = document.querySelector('.active'); // 选择所有p元素下的a元素 const links = document.querySelectorAll('p a'); // 选择具有特定属性的元素 const dataElements = document.querySelectorAll('[data-value]'); 

遍历DOM树

DOM提供了多种属性和方法来遍历节点树:

  1. parentNode:获取父节点。
  2. childNodes:获取所有子节点。
  3. children:获取所有子元素节点。
  4. firstChild:获取第一个子节点。
  5. lastChild:获取最后一个子节点。
  6. nextSibling:获取下一个兄弟节点。
  7. previousSibling:获取上一个兄弟节点。
  8. nextElementSibling:获取下一个兄弟元素节点。
  9. previousElementSibling:获取上一个兄弟元素节点。
// 获取元素的父节点 const parent = element.parentNode; // 获取元素的所有子元素 const children = element.children; // 遍历所有子元素 for (let i = 0; i < children.length; i++) { console.log(children[i]); } // 获取元素的下一个兄弟元素 const nextElement = element.nextElementSibling; 

DOM元素的创建与修改

创建元素

使用DOM API可以动态创建新元素:

  1. createElement():创建新元素。
  2. createTextNode():创建新文本节点。
  3. createDocumentFragment():创建文档片段。
// 创建新元素 const newDiv = document.createElement('div'); newDiv.className = 'container'; newDiv.id = 'main-container'; // 创建文本节点 const text = document.createTextNode('这是一个新段落'); // 将文本节点添加到元素中 newDiv.appendChild(text); // 将元素添加到文档中 document.body.appendChild(newDiv); 

修改元素内容

有几种方法可以修改元素的内容:

  1. innerHTML:设置或获取元素的HTML内容。
  2. textContent:设置或获取元素的文本内容。
  3. innerText:设置或获取元素的可见文本内容(类似于textContent,但考虑样式)。
// 使用innerHTML修改内容 element.innerHTML = '<strong>加粗文本</strong>'; // 使用textContent修改内容 element.textContent = '纯文本内容'; // 使用innerText修改内容 element.innerText = '可见文本内容'; 

注意:使用innerHTML时要注意XSS攻击的风险,特别是当内容来自用户输入时。

修改元素属性

可以使用以下方法修改元素属性:

  1. getAttribute():获取属性值。
  2. setAttribute():设置属性值。
  3. removeAttribute():移除属性。
  4. hasAttribute():检查元素是否有特定属性。
// 设置属性 element.setAttribute('class', 'active'); element.setAttribute('data-id', '123'); // 获取属性 const className = element.getAttribute('class'); const dataId = element.getAttribute('data-id'); // 移除属性 element.removeAttribute('data-id'); // 检查属性 if (element.hasAttribute('class')) { console.log('元素有class属性'); } 

对于一些常见属性,也可以直接通过属性访问:

// 直接通过属性访问 element.id = 'new-id'; element.className = 'new-class'; element.href = 'https://example.com'; 

修改元素样式

可以通过多种方式修改元素的样式:

  1. style属性:直接修改元素的行内样式。
  2. classList:添加、移除或切换元素的类。
  3. getComputedStyle():获取元素的计算样式。
// 使用style属性修改样式 element.style.color = 'red'; element.style.backgroundColor = '#f0f0f0'; element.style.fontSize = '16px'; // 使用classList管理类 element.classList.add('active'); element.classList.remove('inactive'); element.classList.toggle('highlight'); // 检查类是否存在 if (element.classList.contains('active')) { console.log('元素有active类'); } // 获取计算样式 const computedStyle = window.getComputedStyle(element); console.log(computedStyle.color); 

DOM事件处理

事件基础

事件是用户与网页交互的方式,如点击、鼠标移动、键盘输入等。DOM提供了事件处理机制来响应这些交互。

常见的事件类型包括:

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

事件监听器

有几种方式可以添加事件监听器:

  1. HTML事件属性:直接在HTML中定义。
  2. DOM元素属性:通过JavaScript设置元素的事件属性。
  3. addEventListener():使用现代的事件监听方法。
<!-- HTML事件属性 --> <button onclick="alert('Hello!')">点击我</button> 
// DOM元素属性 const button = document.getElementById('myButton'); button.onclick = function() { alert('Hello!'); }; // addEventListener方法 button.addEventListener('click', function() { alert('Hello!'); }); // 使用箭头函数 button.addEventListener('click', () => { alert('Hello!'); }); // 带参数的事件处理函数 function handleClick(message) { return function(event) { alert(message); }; } button.addEventListener('click', handleClick('Hello!')); 

事件冒泡与捕获

事件传播有三个阶段:

  1. 捕获阶段:事件从window对象向下传播到目标元素。
  2. 目标阶段:事件到达目标元素。
  3. 冒泡阶段:事件从目标元素向上传播回window对象。
// 添加捕获阶段的事件监听器 element.addEventListener('click', function() { console.log('捕获阶段'); }, true); // 第三个参数为true表示在捕获阶段触发 // 添加冒泡阶段的事件监听器 element.addEventListener('click', function() { console.log('冒泡阶段'); }, false); // 第三个参数为false(默认)表示在冒泡阶段触发 

可以使用event.stopPropagation()阻止事件继续传播:

element.addEventListener('click', function(event) { console.log('点击事件被触发'); event.stopPropagation(); // 阻止事件冒泡 }); 

事件委托

事件委托是一种利用事件冒泡机制的技术,通过在父元素上设置事件监听器来处理多个子元素的事件。这样可以减少事件监听器的数量,提高性能。

<ul id="item-list"> <li data-id="1">项目1</li> <li data-id="2">项目2</li> <li data-id="3">项目3</li> <li data-id="4">项目4</li> </ul> 
// 使用事件委托处理列表项点击 const itemList = document.getElementById('item-list'); itemList.addEventListener('click', function(event) { // 检查点击的是否是li元素 if (event.target.tagName === 'LI') { const itemId = event.target.getAttribute('data-id'); console.log(`点击了项目 ${itemId}`); // 可以在这里添加更多逻辑,如移除或添加类 event.target.classList.toggle('selected'); } }); 

DOM操作最佳实践

性能优化

DOM操作是昂贵的,频繁的DOM操作会导致性能问题。以下是一些优化技巧:

  1. 减少DOM访问:尽量减少对DOM的访问次数,将DOM引用存储在变量中。
// 不好的做法:多次访问同一个DOM元素 document.getElementById('myElement').style.color = 'red'; document.getElementById('myElement').style.backgroundColor = 'blue'; document.getElementById('myElement').style.fontSize = '16px'; // 好的做法:将DOM引用存储在变量中 const myElement = document.getElementById('myElement'); myElement.style.color = 'red'; myElement.style.backgroundColor = 'blue'; myElement.style.fontSize = '16px'; 
  1. 批量DOM更新:使用文档片段或一次性更新来减少重排和重绘。
// 不好的做法:多次添加元素到DOM const list = document.getElementById('list'); for (let i = 0; i < 100; i++) { const item = document.createElement('li'); item.textContent = `项目 ${i}`; list.appendChild(item); // 每次添加都会导致重排 } // 好的做法:使用文档片段 const list = document.getElementById('list'); const fragment = document.createDocumentFragment(); for (let i = 0; i < 100; i++) { const item = document.createElement('li'); item.textContent = `项目 ${i}`; fragment.appendChild(item); // 添加到文档片段,不会导致重排 } list.appendChild(fragment); // 一次性添加到DOM,只导致一次重排 
  1. 使用事件委托:减少事件监听器的数量。
// 不好的做法:为每个列表项添加事件监听器 const items = document.querySelectorAll('.item'); items.forEach(item => { item.addEventListener('click', function() { console.log('项目被点击'); }); }); // 好的做法:使用事件委托 const list = document.getElementById('list'); list.addEventListener('click', function(event) { if (event.target.classList.contains('item')) { console.log('项目被点击'); } }); 
  1. 避免强制同步布局:避免在JavaScript中读取布局信息后立即修改布局。
// 不好的做法:强制同步布局 function updateElement() { const element = document.getElementById('myElement'); // 读取布局信息(触发重排) const width = element.offsetWidth; // 立即修改布局(再次触发重排) element.style.width = (width + 10) + 'px'; } // 好的做法:批量读取和写入 function updateElement() { const element = document.getElementById('myElement'); // 先读取所有需要的布局信息 const width = element.offsetWidth; const height = element.offsetHeight; // 然后批量修改布局 requestAnimationFrame(() => { element.style.width = (width + 10) + 'px'; element.style.height = (height + 10) + 'px'; }); } 

代码组织

良好的代码组织可以提高代码的可读性和可维护性:

  1. 模块化:将相关功能组织成模块或类。
// DOM操作模块 const DOMUtils = { // 创建元素 createElement(tagName, attributes, content) { const element = document.createElement(tagName); // 设置属性 if (attributes) { for (const key in attributes) { if (attributes.hasOwnProperty(key)) { element.setAttribute(key, attributes[key]); } } } // 设置内容 if (content) { if (typeof content === 'string') { element.textContent = content; } else if (content instanceof HTMLElement) { element.appendChild(content); } } return element; }, // 查找元素 find(selector, context = document) { return context.querySelector(selector); }, // 查找所有元素 findAll(selector, context = document) { return Array.from(context.querySelectorAll(selector)); } }; // 使用模块 const button = DOMUtils.createElement('button', { class: 'btn', id: 'submit' }, '提交'); document.body.appendChild(button); 
  1. 使用设计模式:如观察者模式、策略模式等。
// 观察者模式示例 class EventEmitter { constructor() { this.events = {}; } on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); } emit(event, data) { if (this.events[event]) { this.events[event].forEach(callback => callback(data)); } } off(event, callback) { if (this.events[event]) { this.events[event] = this.events[event].filter(cb => cb !== callback); } } } // 使用观察者模式 const emitter = new EventEmitter(); // 注册事件监听器 emitter.on('click', (data) => { console.log('点击事件:', data); }); // 触发事件 emitter.emit('click', { x: 100, y: 200 }); 

可维护性

编写可维护的DOM操作代码:

  1. 使用语义化的HTML:使HTML结构清晰,易于理解和操作。
<!-- 不好的做法:使用非语义化的div --> <div class="header">...</div> <div class="nav">...</div> <div class="main">...</div> <div class="footer">...</div> <!-- 好的做法:使用语义化的HTML标签 --> <header>...</header> <nav>...</nav> <main>...</main> <footer>...</footer> 
  1. 分离关注点:将HTML、CSS和JavaScript分离。
<!-- index.html --> <button id="myButton">点击我</button> 
/* styles.css */ #myButton { background-color: blue; color: white; padding: 10px 15px; } 
// script.js document.getElementById('myButton').addEventListener('click', function() { alert('按钮被点击'); }); 
  1. 使用常量:避免魔法数字和字符串。
// 不好的做法:使用魔法数字 element.style.fontSize = '16px'; element.style.marginTop = '20px'; // 好的做法:使用常量 const STYLES = { FONT_SIZE: '16px', MARGIN_TOP: '20px' }; element.style.fontSize = STYLES.FONT_SIZE; element.style.marginTop = STYLES.MARGIN_TOP; 

进阶技巧

动态内容加载

动态加载内容可以提升页面性能和用户体验:

  1. AJAX请求:使用Fetch API或XMLHttpRequest获取数据并动态更新DOM。
// 使用Fetch API获取数据 fetch('https://api.example.com/data') .then(response => response.json()) .then(data => { // 处理数据并更新DOM updateDOM(data); }) .catch(error => { console.error('获取数据失败:', error); }); function updateDOM(data) { const container = document.getElementById('data-container'); container.innerHTML = ''; // 清空容器 data.forEach(item => { const element = document.createElement('div'); element.className = 'data-item'; element.textContent = item.name; container.appendChild(element); }); } 
  1. 懒加载:延迟加载非关键资源。
// 图片懒加载 document.addEventListener('DOMContentLoaded', function() { const lazyImages = document.querySelectorAll('img[data-src]'); const lazyLoad = function() { lazyImages.forEach(img => { if (img.getBoundingClientRect().top <= window.innerHeight && img.getBoundingClientRect().bottom >= 0 && getComputedStyle(img).display !== 'none') { img.src = img.dataset.src; img.removeAttribute('data-src'); } }); // 如果所有图片都已加载,移除滚动事件监听器 if (lazyImages.length === 0) { document.removeEventListener('scroll', lazyLoad); window.removeEventListener('resize', lazyLoad); window.removeEventListener('orientationchange', lazyLoad); } }; document.addEventListener('scroll', lazyLoad); window.addEventListener('resize', lazyLoad); window.addEventListener('orientationchange', lazyLoad); lazyLoad(); // 初始加载 }); 
  1. 无限滚动:动态加载更多内容。
// 无限滚动实现 let page = 1; let isLoading = false; function loadMoreContent() { if (isLoading) return; isLoading = true; // 显示加载指示器 const loader = document.getElementById('loader'); loader.style.display = 'block'; // 模拟API请求 setTimeout(() => { const container = document.getElementById('content-container'); // 添加新内容 for (let i = 0; i < 10; i++) { const item = document.createElement('div'); item.className = 'content-item'; item.textContent = `项目 ${page * 10 + i + 1}`; container.appendChild(item); } page++; isLoading = false; loader.style.display = 'none'; }, 1000); } // 监听滚动事件 window.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { loadMoreContent(); } }); // 初始加载 loadMoreContent(); 

动画与过渡

使用DOM操作创建流畅的动画和过渡效果:

  1. CSS过渡:结合CSS和JavaScript实现过渡效果。
/* CSS过渡 */ .fade-in { opacity: 0; transition: opacity 0.5s ease-in-out; } .fade-in.show { opacity: 1; } 
// JavaScript触发过渡 function fadeIn(element) { element.classList.add('fade-in'); // 强制重排以确保过渡效果生效 void element.offsetWidth; element.classList.add('show'); } // 使用示例 const element = document.getElementById('myElement'); fadeIn(element); 
  1. Web Animations API:使用原生JavaScript API创建动画。
// 使用Web Animations API function animateElement() { const element = document.getElementById('myElement'); // 定义关键帧 const keyframes = [ { transform: 'translateX(0px)', opacity: 1 }, { transform: 'translateX(100px)', opacity: 0.5 }, { transform: 'translateX(200px)', opacity: 1 } ]; // 定义动画选项 const options = { duration: 1000, iterations: 1, easing: 'ease-in-out' }; // 应用动画 const animation = element.animate(keyframes, options); // 监听动画结束事件 animation.onfinish = () => { console.log('动画完成'); }; } // 使用示例 document.getElementById('animateButton').addEventListener('click', animateElement); 
  1. Intersection Observer API:基于元素可见性触发动画。
// 使用Intersection Observer API实现滚动动画 function setupScrollAnimations() { const elements = document.querySelectorAll('.animate-on-scroll'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { // 如果元素可见 if (entry.isIntersecting) { entry.target.classList.add('animated'); // 动画执行后取消观察 observer.unobserve(entry.target); } }); }, { threshold: 0.1 // 元素10%可见时触发 }); elements.forEach(element => { observer.observe(element); }); } // 初始化 document.addEventListener('DOMContentLoaded', setupScrollAnimations); 

响应式DOM操作

根据不同的设备和屏幕尺寸调整DOM操作:

  1. 媒体查询:使用JavaScript响应CSS媒体查询。
// 监听媒体查询变化 const mediaQuery = window.matchMedia('(min-width: 768px)'); function handleMediaChange(e) { if (e.matches) { // 屏幕宽度大于等于768px console.log('大屏幕模式'); // 执行大屏幕相关的DOM操作 adjustLayoutForDesktop(); } else { // 屏幕宽度小于768px console.log('小屏幕模式'); // 执行小屏幕相关的DOM操作 adjustLayoutForMobile(); } } // 添加监听器 mediaQuery.addListener(handleMediaChange); // 初始检查 handleMediaChange(mediaQuery); function adjustLayoutForDesktop() { // 桌面布局调整 const sidebar = document.getElementById('sidebar'); const content = document.getElementById('content'); sidebar.style.display = 'block'; content.style.width = '75%'; } function adjustLayoutForMobile() { // 移动布局调整 const sidebar = document.getElementById('sidebar'); const content = document.getElementById('content'); sidebar.style.display = 'none'; content.style.width = '100%'; } 
  1. 触摸事件:处理触摸设备的交互。
// 处理触摸事件 function setupTouchEvents() { const element = document.getElementById('touch-element'); let startX = 0; let startY = 0; // 触摸开始 element.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; startY = e.touches[0].clientY; element.style.transition = 'none'; }); // 触摸移动 element.addEventListener('touchmove', (e) => { const currentX = e.touches[0].clientX; const currentY = e.touches[0].clientY; const diffX = currentX - startX; const diffY = currentY - startY; // 水平滑动 if (Math.abs(diffX) > Math.abs(diffY)) { e.preventDefault(); // 防止垂直滚动 element.style.transform = `translateX(${diffX}px)`; } }); // 触摸结束 element.addEventListener('touchend', (e) => { element.style.transition = 'transform 0.3s ease-out'; element.style.transform = 'translateX(0)'; }); } // 初始化 document.addEventListener('DOMContentLoaded', setupTouchEvents); 

实用案例

表单验证

使用DOM操作实现表单验证:

<form id="registration-form" novalidate> <div class="form-group"> <label for="username">用户名</label> <input type="text" id="username" name="username" required minlength="3"> <div class="error-message"></div> </div> <div class="form-group"> <label for="email">电子邮件</label> <input type="email" id="email" name="email" required> <div class="error-message"></div> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" id="password" name="password" required minlength="6"> <div class="error-message"></div> </div> <div class="form-group"> <label for="confirm-password">确认密码</label> <input type="password" id="confirm-password" name="confirm-password" required> <div class="error-message"></div> </div> <button type="submit">注册</button> </form> 
// 表单验证 document.addEventListener('DOMContentLoaded', function() { const form = document.getElementById('registration-form'); // 实时验证 const inputs = form.querySelectorAll('input'); inputs.forEach(input => { input.addEventListener('blur', () => validateField(input)); input.addEventListener('input', () => { // 清除错误消息 const errorElement = input.parentNode.querySelector('.error-message'); if (errorElement) { errorElement.textContent = ''; } }); }); // 表单提交验证 form.addEventListener('submit', function(event) { event.preventDefault(); let isValid = true; // 验证所有字段 inputs.forEach(input => { if (!validateField(input)) { isValid = false; } }); // 验证密码匹配 const password = document.getElementById('password').value; const confirmPassword = document.getElementById('confirm-password').value; if (password !== confirmPassword) { const errorElement = document.getElementById('confirm-password').parentNode.querySelector('.error-message'); errorElement.textContent = '密码不匹配'; isValid = false; } if (isValid) { // 表单验证通过,可以提交 console.log('表单验证通过,准备提交'); // 这里可以添加表单提交逻辑 // form.submit(); } }); // 验证单个字段 function validateField(input) { const errorElement = input.parentNode.querySelector('.error-message'); let isValid = true; let errorMessage = ''; // 检查必填项 if (input.hasAttribute('required') && !input.value.trim()) { isValid = false; errorMessage = '此字段为必填项'; } // 检查最小长度 if (input.hasAttribute('minlength') && input.value.length < parseInt(input.getAttribute('minlength'))) { isValid = false; errorMessage = `至少需要${input.getAttribute('minlength')}个字符`; } // 检查电子邮件格式 if (input.type === 'email' && input.value && !isValidEmail(input.value)) { isValid = false; errorMessage = '请输入有效的电子邮件地址'; } // 显示错误消息 if (errorElement) { errorElement.textContent = errorMessage; } // 添加/移除错误样式 if (isValid) { input.classList.remove('error'); } else { input.classList.add('error'); } return isValid; } // 验证电子邮件格式 function isValidEmail(email) { const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/; return emailRegex.test(email); } }); 

动态列表

创建一个可添加、删除和编辑项目的动态列表:

<div class="list-container"> <h2>待办事项</h2> <div class="input-container"> <input type="text" id="new-item" placeholder="添加新项目..."> <button id="add-btn">添加</button> </div> <ul id="todo-list" class="todo-list"></ul> <div class="stats"> <span id="total-count">总计: 0</span> <span id="completed-count">已完成: 0</span> </div> </div> 
// 动态列表 document.addEventListener('DOMContentLoaded', function() { const newItemInput = document.getElementById('new-item'); const addBtn = document.getElementById('add-btn'); const todoList = document.getElementById('todo-list'); const totalCount = document.getElementById('total-count'); const completedCount = document.getElementById('completed-count'); // 初始化示例数据 let todos = [ { id: 1, text: '学习JavaScript', completed: false }, { id: 2, text: '掌握DOM操作', completed: true }, { id: 3, text: '构建Web应用', completed: false } ]; // 渲染列表 renderTodos(); // 添加项目 addBtn.addEventListener('click', addTodo); newItemInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') { addTodo(); } }); // 添加新项目 function addTodo() { const text = newItemInput.value.trim(); if (text) { const newTodo = { id: Date.now(), text: text, completed: false }; todos.push(newTodo); newItemInput.value = ''; renderTodos(); } } // 删除项目 function deleteTodo(id) { todos = todos.filter(todo => todo.id !== id); renderTodos(); } // 切换完成状态 function toggleTodo(id) { todos = todos.map(todo => { if (todo.id === id) { return { ...todo, completed: !todo.completed }; } return todo; }); renderTodos(); } // 编辑项目 function editTodo(id, newText) { todos = todos.map(todo => { if (todo.id === id) { return { ...todo, text: newText }; } return todo; }); renderTodos(); } // 渲染列表 function renderTodos() { // 清空列表 todoList.innerHTML = ''; // 渲染每个项目 todos.forEach(todo => { const li = document.createElement('li'); li.className = `todo-item ${todo.completed ? 'completed' : ''}`; li.dataset.id = todo.id; // 复选框 const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.className = 'todo-checkbox'; checkbox.checked = todo.completed; checkbox.addEventListener('change', () => toggleTodo(todo.id)); // 文本 const text = document.createElement('span'); text.className = 'todo-text'; text.textContent = todo.text; // 编辑按钮 const editBtn = document.createElement('button'); editBtn.className = 'edit-btn'; editBtn.textContent = '编辑'; editBtn.addEventListener('click', () => { const currentText = todo.text; const input = document.createElement('input'); input.type = 'text'; input.className = 'edit-input'; input.value = currentText; // 替换文本为输入框 text.parentNode.replaceChild(input, text); input.focus(); input.select(); // 保存编辑 function saveEdit() { const newText = input.value.trim(); if (newText && newText !== currentText) { editTodo(todo.id, newText); } else { renderTodos(); } } // 监听事件 input.addEventListener('blur', saveEdit); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') { saveEdit(); } }); }); // 删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-btn'; deleteBtn.textContent = '删除'; deleteBtn.addEventListener('click', () => deleteTodo(todo.id)); // 添加元素到列表项 li.appendChild(checkbox); li.appendChild(text); li.appendChild(editBtn); li.appendChild(deleteBtn); // 添加列表项到列表 todoList.appendChild(li); }); // 更新统计 updateStats(); } // 更新统计 function updateStats() { const total = todos.length; const completed = todos.filter(todo => todo.completed).length; totalCount.textContent = `总计: ${total}`; completedCount.textContent = `已完成: ${completed}`; } }); 

模态框

创建一个可重用的模态框组件:

<!-- 模态框HTML结构 --> <div class="modal-overlay" id="modal-overlay"> <div class="modal-container"> <div class="modal-header"> <h3 class="modal-title" id="modal-title">模态框标题</h3> <button class="modal-close" id="modal-close">&times;</button> </div> <div class="modal-body" id="modal-body"> 模态框内容 </div> <div class="modal-footer" id="modal-footer"> <button class="modal-btn modal-btn-primary" id="modal-confirm">确认</button> <button class="modal-btn modal-btn-secondary" id="modal-cancel">取消</button> </div> </div> </div> <!-- 触发按钮 --> <button id="show-modal-btn">显示模态框</button> 
// 模态框组件 document.addEventListener('DOMContentLoaded', function() { const showModalBtn = document.getElementById('show-modal-btn'); const modalOverlay = document.getElementById('modal-overlay'); const modalTitle = document.getElementById('modal-title'); const modalBody = document.getElementById('modal-body'); const modalFooter = document.getElementById('modal-footer'); const modalClose = document.getElementById('modal-close'); const modalConfirm = document.getElementById('modal-confirm'); const modalCancel = document.getElementById('modal-cancel'); // 显示模态框 showModalBtn.addEventListener('click', function() { showModal({ title: '确认删除', body: '您确定要删除这个项目吗?此操作无法撤销。', confirmText: '删除', cancelText: '取消', onConfirm: function() { console.log('项目已删除'); // 这里可以添加删除逻辑 } }); }); // 显示模态框函数 function showModal(options) { // 设置标题 modalTitle.textContent = options.title || '提示'; // 设置内容 if (typeof options.body === 'string') { modalBody.innerHTML = options.body; } else if (options.body instanceof HTMLElement) { modalBody.innerHTML = ''; modalBody.appendChild(options.body); } // 设置按钮文本 modalConfirm.textContent = options.confirmText || '确认'; modalCancel.textContent = options.cancelText || '取消'; // 显示或隐藏取消按钮 modalCancel.style.display = options.showCancel !== false ? 'inline-block' : 'none'; // 设置确认按钮回调 modalConfirm.onclick = function() { if (options.onConfirm && typeof options.onConfirm === 'function') { options.onConfirm(); } hideModal(); }; // 设置取消按钮回调 modalCancel.onclick = function() { if (options.onCancel && typeof options.onCancel === 'function') { options.onCancel(); } hideModal(); }; // 显示模态框 modalOverlay.style.display = 'flex'; // 防止背景滚动 document.body.style.overflow = 'hidden'; } // 隐藏模态框 function hideModal() { modalOverlay.style.display = 'none'; document.body.style.overflow = ''; } // 关闭按钮点击事件 modalClose.addEventListener('click', hideModal); // 点击遮罩层关闭模态框 modalOverlay.addEventListener('click', function(event) { if (event.target === modalOverlay) { hideModal(); } }); // ESC键关闭模态框 document.addEventListener('keydown', function(event) { if (event.key === 'Escape' && modalOverlay.style.display === 'flex') { hideModal(); } }); }); 

总结与展望

通过本文的学习,你已经从基础到进阶全面掌握了HTML DOM元素脚本编写的实用技巧与最佳实践。我们涵盖了DOM基础、元素选择与访问、元素创建与修改、事件处理、性能优化、代码组织、可维护性以及进阶技巧等多个方面,并通过实际案例展示了如何将这些知识应用到实际开发中。

掌握DOM操作是成为一名专业前端开发者的必备技能,它能够帮助你创建更加动态、交互性强的网页应用,提升用户体验。随着前端技术的不断发展,DOM操作也在不断演进,新的API和最佳实践不断涌现。

未来,你可以继续深入学习以下方面:

  1. Web Components:创建可重用的自定义元素。
  2. Shadow DOM:实现封装的DOM和样式。
  3. 虚拟DOM:了解React等框架如何通过虚拟DOM提高性能。
  4. 现代框架:学习如何使用Vue、React等框架简化DOM操作。
  5. 性能优化:深入研究DOM性能优化技术,如减少重排和重绘。

希望本文能够帮助你成为一名专业的前端开发者,在Web开发的道路上取得更大的成就!