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

站内搜索

搜索

活动公告

通知:为庆祝网站一周年,将在5.1日与5.2日开放注册,具体信息请见后续详细公告
04-22 00:04
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

深入浅出HTML DOM查询与遍历 从基础选择器到高级树形结构操作全面解析前端开发必备技能提升网页交互性能

SunJu_FaceMall

3万

主题

1158

科技点

3万

积分

白金月票

碾压王

积分
32796

立华奏

发表于 2025-10-2 00:20:10 | 显示全部楼层 |阅读模式

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

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

x
1. DOM基础概念

文档对象模型(Document Object Model,简称DOM)是HTML和XML文档的编程接口,它将文档表示为一个节点树,允许程序和脚本动态地访问和更新文档的内容、结构和样式。DOM是前端开发的核心技术之一,理解并熟练掌握DOM操作对于构建交互式网页至关重要。

在DOM中,每个HTML元素都被视为一个节点,整个HTML文档构成一个树形结构。文档节点是树的根节点,每个HTML元素是一个元素节点,元素内的文本是文本节点,元素的属性是属性节点。
  1. <!DOCTYPE html>
  2. <html>
  3.   <head>
  4.     <title>DOM示例</title>
  5.   </head>
  6.   <body>
  7.     <h1>DOM树形结构</h1>
  8.     <p>这是一个段落。</p>
  9.   </body>
  10. </html>
复制代码

在上述HTML结构中,DOM树形结构如下:

• 文档节点(Document)html元素节点head元素节点title元素节点“DOM示例”文本节点body元素节点h1元素节点“DOM树形结构”文本节点p元素节点“这是一个段落。”文本节点
• html元素节点head元素节点title元素节点“DOM示例”文本节点body元素节点h1元素节点“DOM树形结构”文本节点p元素节点“这是一个段落。”文本节点
• head元素节点title元素节点“DOM示例”文本节点
• title元素节点
• “DOM示例”文本节点
• body元素节点h1元素节点“DOM树形结构”文本节点p元素节点“这是一个段落。”文本节点
• h1元素节点
• “DOM树形结构”文本节点
• p元素节点
• “这是一个段落。”文本节点

• html元素节点head元素节点title元素节点“DOM示例”文本节点body元素节点h1元素节点“DOM树形结构”文本节点p元素节点“这是一个段落。”文本节点
• head元素节点title元素节点“DOM示例”文本节点
• title元素节点
• “DOM示例”文本节点
• body元素节点h1元素节点“DOM树形结构”文本节点p元素节点“这是一个段落。”文本节点
• h1元素节点
• “DOM树形结构”文本节点
• p元素节点
• “这是一个段落。”文本节点

• head元素节点title元素节点“DOM示例”文本节点
• title元素节点
• “DOM示例”文本节点
• body元素节点h1元素节点“DOM树形结构”文本节点p元素节点“这是一个段落。”文本节点
• h1元素节点
• “DOM树形结构”文本节点
• p元素节点
• “这是一个段落。”文本节点

• title元素节点
• “DOM示例”文本节点

• h1元素节点
• “DOM树形结构”文本节点
• p元素节点
• “这是一个段落。”文本节点

2. 基础选择器

2.1 getElementById

getElementById是最基础也是最常用的DOM查询方法,它通过元素的ID属性返回对应的元素节点。由于ID在HTML文档中是唯一的,所以这个方法总是返回单个元素或null(如果不存在)。
  1. // 获取ID为"header"的元素
  2. const headerElement = document.getElementById('header');
  3. // 修改元素内容
  4. if (headerElement) {
  5.   headerElement.textContent = '新的标题';
  6. }
复制代码

2.2 getElementsByTagName

getElementsByTagName方法通过元素的标签名返回一个HTMLCollection(类似数组的集合),包含文档中所有匹配的元素。
  1. // 获取所有的段落元素
  2. const paragraphs = document.getElementsByTagName('p');
  3. // 遍历所有段落并修改样式
  4. for (let i = 0; i < paragraphs.length; i++) {
  5.   paragraphs[i].style.color = 'blue';
  6. }
  7. // 注意:HTMLCollection是动态的,当文档变化时会自动更新
  8. // 这意味着如果你在循环中添加新的p元素,循环可能会无限进行
  9. // 解决方法是将HTMLCollection转换为数组
  10. const paragraphsArray = Array.from(paragraphs);
复制代码

2.3 getElementsByClassName

getElementsByClassName方法通过元素的class属性返回一个HTMLCollection,包含文档中所有具有指定类名的元素。
  1. // 获取所有类名为"highlight"的元素
  2. const highlightedElements = document.getElementsByClassName('highlight');
  3. // 为所有高亮元素添加边框
  4. Array.from(highlightedElements).forEach(element => {
  5.   element.style.border = '1px solid yellow';
  6. });
复制代码

2.4 getElementsByName

getElementsByName方法通过元素的name属性返回一个NodeList,包含文档中所有具有指定名称的元素。这个方法主要用于表单元素。
  1. // 获取所有name为"gender"的元素(通常是单选按钮)
  2. const genderInputs = document.getElementsByName('gender');
  3. // 检查哪个单选按钮被选中
  4. let selectedGender;
  5. for (let i = 0; i < genderInputs.length; i++) {
  6.   if (genderInputs[i].checked) {
  7.     selectedGender = genderInputs[i].value;
  8.     break;
  9.   }
  10. }
  11. console.log('选中的性别是:', selectedGender);
复制代码

3. 高级选择器

3.1 querySelector

querySelector方法接受一个CSS选择器字符串,返回文档中匹配该选择器的第一个元素。如果没有匹配的元素,则返回null。
  1. // 获取第一个类名为"container"的div元素
  2. const container = document.querySelector('div.container');
  3. // 获取ID为"main"的元素下的第一个p元素
  4. const firstParagraph = document.querySelector('#main p');
  5. // 获取具有data-id属性且值为"123"的元素
  6. const specificElement = document.querySelector('[data-id="123"]');
复制代码

3.2 querySelectorAll

querySelectorAll方法与querySelector类似,但它返回一个NodeList,包含文档中所有匹配该选择器的元素。NodeList不是动态的,它是一个快照,即使文档发生变化也不会更新。
  1. // 获取所有类名为"item"的li元素
  2. const items = document.querySelectorAll('li.item');
  3. // 为所有项目添加点击事件
  4. items.forEach(item => {
  5.   item.addEventListener('click', function() {
  6.     console.log('点击了项目:', this.textContent);
  7.   });
  8. });
  9. // 使用更复杂的选择器
  10. // 获取类名为"active"的div元素下的所有直接子元素p
  11. const activeParagraphs = document.querySelectorAll('div.active > p');
复制代码

3.3 matches方法

matches方法检查元素是否匹配指定的CSS选择器,返回true或false。这个方法对于元素验证非常有用。
  1. // 检查元素是否具有特定类
  2. const element = document.querySelector('#myElement');
  3. if (element.matches('.highlight')) {
  4.   console.log('元素具有highlight类');
  5. }
  6. // 在事件处理中使用matches
  7. document.addEventListener('click', function(event) {
  8.   // 检查点击的元素是否是按钮
  9.   if (event.target.matches('button')) {
  10.     console.log('点击了按钮');
  11.   }
  12. });
复制代码

3.4 closest方法

closest方法从当前元素开始,沿DOM树向上查找匹配指定CSS选择器的最近的祖先元素(包括当前元素本身)。如果没有找到,则返回null。
  1. // 查找最近的具有"container"类的祖先元素
  2. const element = document.querySelector('.item');
  3. const container = element.closest('.container');
  4. if (container) {
  5.   console.log('找到了容器元素');
  6. }
  7. // 在事件委托中使用closest
  8. document.addEventListener('click', function(event) {
  9.   // 查找最近的具有"delete-button"类的元素
  10.   const deleteButton = event.target.closest('.delete-button');
  11.   
  12.   if (deleteButton) {
  13.     const itemToDelete = deleteButton.closest('.item');
  14.     if (itemToDelete) {
  15.       itemToDelete.remove();
  16.       console.log('删除了项目');
  17.     }
  18.   }
  19. });
复制代码

4. DOM节点关系与遍历

4.1 节点类型

在DOM中,有几种不同类型的节点,每种类型都有一个对应的数值常量:
  1. // 节点类型常量
  2. console.log(Node.ELEMENT_NODE); // 1 - 元素节点
  3. console.log(Node.TEXT_NODE); // 3 - 文本节点
  4. console.log(Node.COMMENT_NODE); // 8 - 注释节点
  5. console.log(Node.DOCUMENT_NODE); // 9 - 文档节点
  6. console.log(Node.DOCUMENT_FRAGMENT_NODE); // 11 - 文档片段节点
  7. // 检查节点类型
  8. const element = document.querySelector('div');
  9. console.log(element.nodeType === Node.ELEMENT_NODE); // true
复制代码

4.2 节点关系属性

DOM节点提供了多个属性来访问相关节点:
  1. // 获取元素
  2. const element = document.querySelector('#myElement');
  3. // 父节点
  4. const parent = element.parentNode; // 或 parentElement
  5. // 子节点
  6. const firstChild = element.firstChild; // 第一个子节点(可能是文本节点)
  7. const lastChild = element.lastChild; // 最后一个子节点(可能是文本节点)
  8. const childNodes = element.childNodes; // 所有子节点的NodeList
  9. // 兄弟节点
  10. const previousSibling = element.previousSibling; // 前一个兄弟节点(可能是文本节点)
  11. const nextSibling = element.nextSibling; // 后一个兄弟节点(可能是文本节点)
  12. // 只考虑元素节点的属性
  13. const firstElementChild = element.firstElementChild; // 第一个元素子节点
  14. const lastElementChild = element.lastElementChild; // 最后一个元素子节点
  15. const previousElementSibling = element.previousElementSibling; // 前一个元素兄弟节点
  16. const nextElementSibling = element.nextElementSibling; // 后一个元素兄弟节点
  17. const children = element.children; // 所有元素子节点的HTMLCollection
复制代码

4.3 遍历DOM树

使用节点关系属性,我们可以编写函数来遍历DOM树:
  1. // 深度优先遍历DOM树
  2. function traverseDOM(node, callback) {
  3.   // 处理当前节点
  4.   if (callback) {
  5.     callback(node);
  6.   }
  7.   
  8.   // 递归处理子节点
  9.   for (let i = 0; i < node.childNodes.length; i++) {
  10.     traverseDOM(node.childNodes[i], callback);
  11.   }
  12. }
  13. // 使用示例
  14. traverseDOM(document.body, function(node) {
  15.   if (node.nodeType === Node.ELEMENT_NODE) {
  16.     console.log('元素节点:', node.tagName.toLowerCase());
  17.   }
  18. });
  19. // 广度优先遍历DOM树
  20. function traverseDOMWidthFirst(startNode, callback) {
  21.   const queue = [startNode];
  22.   
  23.   while (queue.length > 0) {
  24.     const node = queue.shift();
  25.    
  26.     // 处理当前节点
  27.     if (callback) {
  28.       callback(node);
  29.     }
  30.    
  31.     // 将子节点加入队列
  32.     for (let i = 0; i < node.childNodes.length; i++) {
  33.       queue.push(node.childNodes[i]);
  34.     }
  35.   }
  36. }
  37. // 使用示例
  38. traverseDOMWidthFirst(document.body, function(node) {
  39.   if (node.nodeType === Node.ELEMENT_NODE) {
  40.     console.log('元素节点:', node.tagName.toLowerCase());
  41.   }
  42. });
复制代码

4.4 TreeWalker API

TreeWalker API提供了一个更高级的方式来遍历DOM树,它允许我们定义遍历的过滤条件。
  1. // 创建TreeWalker
  2. const walker = document.createTreeWalker(
  3.   document.body, // 根节点
  4.   NodeFilter.SHOW_ELEMENT, // 只显示元素节点
  5.   { // 过滤器对象
  6.     acceptNode: function(node) {
  7.       // 只接受具有"active"类的元素
  8.       if (node.classList.contains('active')) {
  9.         return NodeFilter.FILTER_ACCEPT;
  10.       }
  11.       return NodeFilter.FILTER_SKIP;
  12.     }
  13.   }
  14. );
  15. // 遍历所有符合条件的节点
  16. let currentNode;
  17. while (currentNode = walker.nextNode()) {
  18.   console.log('找到活动元素:', currentNode);
  19. }
复制代码

4.5 NodeIterator API

NodeIterator是另一个遍历DOM树的API,它比TreeWalker简单一些。
  1. // 创建NodeIterator
  2. const iterator = document.createNodeIterator(
  3.   document.body,
  4.   NodeFilter.SHOW_TEXT, // 只显示文本节点
  5.   {
  6.     acceptNode: function(node) {
  7.       // 忽略只包含空白字符的文本节点
  8.       if (node.nodeValue.trim() === '') {
  9.         return NodeFilter.FILTER_REJECT;
  10.       }
  11.       return NodeFilter.FILTER_ACCEPT;
  12.     }
  13.   }
  14. );
  15. // 遍历所有符合条件的节点
  16. let currentNode;
  17. while (currentNode = iterator.nextNode()) {
  18.   console.log('找到文本节点:', currentNode.nodeValue.trim());
  19. }
复制代码

5. 树形结构操作

5.1 创建节点

DOM提供了多种方法来创建新节点:
  1. // 创建元素节点
  2. const newDiv = document.createElement('div');
  3. newDiv.className = 'container';
  4. newDiv.id = 'main-container';
  5. // 创建文本节点
  6. const text = document.createTextNode('这是一个文本节点');
  7. // 创建注释节点
  8. const comment = document.createComment('这是一个注释');
  9. // 创建文档片段
  10. const fragment = document.createDocumentFragment();
  11. // 创建属性节点
  12. const attr = document.createAttribute('data-id');
  13. attr.value = '123';
  14. newDiv.setAttributeNode(attr);
复制代码

5.2 插入节点

有多种方法可以将新节点插入到DOM树中:
  1. // 获取父元素和子元素
  2. const parent = document.getElementById('parent');
  3. const child = document.getElementById('child');
  4. // appendChild - 在父元素的末尾添加子元素
  5. const newElement = document.createElement('div');
  6. newElement.textContent = '新元素';
  7. parent.appendChild(newElement);
  8. // insertBefore - 在指定子元素之前插入新元素
  9. const anotherNewElement = document.createElement('div');
  10. anotherNewElement.textContent = '另一个新元素';
  11. parent.insertBefore(anotherNewElement, child);
  12. // insertAdjacentHTML - 在元素的指定位置插入HTML字符串
  13. const referenceElement = document.getElementById('reference');
  14. referenceElement.insertAdjacentHTML('beforebegin', '<div>在元素之前</div>');
  15. referenceElement.insertAdjacentHTML('afterbegin', '<div>在元素内部开始处</div>');
  16. referenceElement.insertAdjacentHTML('beforeend', '<div>在元素内部结束处</div>');
  17. referenceElement.insertAdjacentHTML('afterend', '<div>在元素之后</div>');
  18. // insertAdjacentElement - 类似于insertAdjacentHTML,但插入的是DOM元素
  19. const newElement1 = document.createElement('div');
  20. newElement1.textContent = '新元素1';
  21. referenceElement.insertAdjacentElement('beforebegin', newElement1);
  22. // 使用文档片段批量插入,减少重排
  23. const fragment = document.createDocumentFragment();
  24. for (let i = 0; i < 10; i++) {
  25.   const li = document.createElement('li');
  26.   li.textContent = `项目 ${i + 1}`;
  27.   fragment.appendChild(li);
  28. }
  29. document.getElementById('list').appendChild(fragment);
复制代码

5.3 替换和删除节点
  1. // 获取元素
  2. const parent = document.getElementById('parent');
  3. const oldChild = document.getElementById('old-child');
  4. // replaceChild - 替换子元素
  5. const newChild = document.createElement('div');
  6. newChild.textContent = '新子元素';
  7. parent.replaceChild(newChild, oldChild);
  8. // removeChild - 删除子元素
  9. const childToRemove = document.getElementById('child-to-remove');
  10. parent.removeChild(childToRemove);
  11. // 现代方法:直接在元素上调用remove
  12. const elementToRemove = document.getElementById('element-to-remove');
  13. elementToRemove.remove();
复制代码

5.4 克隆节点
  1. // 获取元素
  2. const original = document.getElementById('original');
  3. // cloneNode - 克隆节点
  4. // 参数为true时进行深度克隆(包括所有后代节点)
  5. // 参数为false时进行浅克隆(不包括后代节点)
  6. const shallowClone = original.cloneNode(false);
  7. const deepClone = original.cloneNode(true);
  8. // 将克隆的节点添加到文档中
  9. document.body.appendChild(deepClone);
复制代码

5.5 操作属性
  1. // 获取元素
  2. const element = document.getElementById('myElement');
  3. // getAttribute - 获取属性值
  4. const id = element.getAttribute('id');
  5. const className = element.getAttribute('class');
  6. // setAttribute - 设置属性值
  7. element.setAttribute('class', 'new-class');
  8. element.setAttribute('data-id', '123');
  9. // hasAttribute - 检查元素是否有指定属性
  10. if (element.hasAttribute('data-id')) {
  11.   console.log('元素有data-id属性');
  12. }
  13. // removeAttribute - 移除属性
  14. element.removeAttribute('data-id');
  15. // 直接属性访问(对于标准属性)
  16. element.id = 'new-id';
  17. element.className = 'another-class';
  18. // dataset属性 - 访问data-*属性
  19. element.dataset.id = '456'; // 设置data-id
  20. console.log(element.dataset.id); // 获取data-id
  21. console.log(element.dataset.someValue); // 获取data-some-value
复制代码

6. 性能优化技巧

6.1 减少DOM访问

DOM访问是相对昂贵的操作,频繁访问DOM会影响性能。应该尽量减少DOM访问次数:
  1. // 不好的做法 - 在循环中多次访问DOM
  2. const items = document.querySelectorAll('.item');
  3. for (let i = 0; i < items.length; i++) {
  4.   items[i].style.color = 'red';
  5.   items[i].style.backgroundColor = 'yellow';
  6.   items[i].style.border = '1px solid black';
  7. }
  8. // 好的做法 - 减少DOM访问
  9. const itemsArray = Array.from(document.querySelectorAll('.item'));
  10. const styles = {
  11.   color: 'red',
  12.   backgroundColor: 'yellow',
  13.   border: '1px solid black'
  14. };
  15. itemsArray.forEach(item => {
  16.   Object.assign(item.style, styles);
  17. });
复制代码

6.2 使用文档片段

文档片段(DocumentFragment)是一个轻量级的DOM对象,可以在内存中操作DOM,然后一次性插入到文档中,减少重排和重绘:
  1. // 不好的做法 - 多次插入导致多次重排
  2. const list = document.getElementById('list');
  3. for (let i = 0; i < 100; i++) {
  4.   const li = document.createElement('li');
  5.   li.textContent = `项目 ${i + 1}`;
  6.   list.appendChild(li); // 每次appendChild都会导致重排
  7. }
  8. // 好的做法 - 使用文档片段
  9. const list = document.getElementById('list');
  10. const fragment = document.createDocumentFragment();
  11. for (let i = 0; i < 100; i++) {
  12.   const li = document.createElement('li');
  13.   li.textContent = `项目 ${i + 1}`;
  14.   fragment.appendChild(li); // 在内存中操作,不会导致重排
  15. }
  16. list.appendChild(fragment); // 只导致一次重排
复制代码

6.3 批量修改样式

修改元素样式会导致重排和重绘,应该尽量批量修改样式:
  1. // 不好的做法 - 多次修改样式
  2. const element = document.getElementById('myElement');
  3. element.style.width = '100px';
  4. element.style.height = '100px';
  5. element.style.backgroundColor = 'red';
  6. element.style.color = 'white';
  7. // 好的做法 - 批量修改样式
  8. const element = document.getElementById('myElement');
  9. element.style.cssText = 'width: 100px; height: 100px; background-color: red; color: white;';
  10. // 或者使用类
  11. const element = document.getElementById('myElement');
  12. element.className = 'highlighted-box';
复制代码

6.4 使用事件委托

事件委托利用事件冒泡机制,将事件监听器添加到父元素上,而不是每个子元素上:
  1. // 不好的做法 - 为每个按钮添加事件监听器
  2. const buttons = document.querySelectorAll('.button');
  3. buttons.forEach(button => {
  4.   button.addEventListener('click', function() {
  5.     console.log('按钮被点击:', this.textContent);
  6.   });
  7. });
  8. // 好的做法 - 使用事件委托
  9. document.getElementById('button-container').addEventListener('click', function(event) {
  10.   if (event.target.matches('.button')) {
  11.     console.log('按钮被点击:', event.target.textContent);
  12.   }
  13. });
复制代码

6.5 使用requestAnimationFrame

对于需要频繁更新DOM的操作(如动画),使用requestAnimationFrame可以优化性能:
  1. // 不好的做法 - 使用setTimeout或setInterval
  2. let position = 0;
  3. function animate() {
  4.   position += 1;
  5.   element.style.transform = `translateX(${position}px)`;
  6.   setTimeout(animate, 16); // 大约60fps
  7. }
  8. animate();
  9. // 好的做法 - 使用requestAnimationFrame
  10. let position = 0;
  11. function animate() {
  12.   position += 1;
  13.   element.style.transform = `translateX(${position}px)`;
  14.   requestAnimationFrame(animate);
  15. }
  16. requestAnimationFrame(animate);
复制代码

6.6 虚拟滚动

对于包含大量数据的列表,使用虚拟滚动可以显著提高性能:
  1. // 虚拟滚动实现示例
  2. class VirtualScroll {
  3.   constructor(container, itemHeight, totalItems) {
  4.     this.container = container;
  5.     this.itemHeight = itemHeight;
  6.     this.totalItems = totalItems;
  7.     this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2;
  8.     this.scrollTop = 0;
  9.    
  10.     // 创建占位元素以设置滚动高度
  11.     this.placeholder = document.createElement('div');
  12.     this.placeholder.style.height = `${totalItems * itemHeight}px`;
  13.     container.appendChild(this.placeholder);
  14.    
  15.     // 创建可见项容器
  16.     this.itemsContainer = document.createElement('div');
  17.     container.appendChild(this.itemsContainer);
  18.    
  19.     // 监听滚动事件
  20.     container.addEventListener('scroll', () => this.handleScroll());
  21.    
  22.     // 初始渲染
  23.     this.renderItems();
  24.   }
  25.   
  26.   handleScroll() {
  27.     this.scrollTop = this.container.scrollTop;
  28.     this.renderItems();
  29.   }
  30.   
  31.   renderItems() {
  32.     // 计算起始索引
  33.     const startIndex = Math.floor(this.scrollTop / this.itemHeight);
  34.    
  35.     // 清空容器
  36.     this.itemsContainer.innerHTML = '';
  37.    
  38.     // 设置容器位置
  39.     this.itemsContainer.style.transform = `translateY(${startIndex * this.itemHeight}px)`;
  40.    
  41.     // 渲染可见项
  42.     for (let i = 0; i < this.visibleItems; i++) {
  43.       const index = startIndex + i;
  44.       if (index >= this.totalItems) break;
  45.       
  46.       const item = document.createElement('div');
  47.       item.style.height = `${this.itemHeight}px`;
  48.       item.textContent = `项目 ${index + 1}`;
  49.       this.itemsContainer.appendChild(item);
  50.     }
  51.   }
  52. }
  53. // 使用示例
  54. const container = document.getElementById('scroll-container');
  55. const virtualScroll = new VirtualScroll(container, 30, 1000);
复制代码

7. 实际应用场景和示例

7.1 动态表格排序
  1. class TableSorter {
  2.   constructor(tableId) {
  3.     this.table = document.getElementById(tableId);
  4.     this.headers = this.table.querySelectorAll('th');
  5.     this.tbody = this.table.querySelector('tbody');
  6.     this.rows = Array.from(this.tbody.querySelectorAll('tr'));
  7.     this.sortColumn = -1;
  8.     this.sortDirection = 1;
  9.    
  10.     // 为表头添加点击事件
  11.     this.headers.forEach((header, index) => {
  12.       header.addEventListener('click', () => this.sort(index));
  13.     });
  14.   }
  15.   
  16.   sort(columnIndex) {
  17.     // 如果点击的是同一列,则切换排序方向
  18.     if (this.sortColumn === columnIndex) {
  19.       this.sortDirection *= -1;
  20.     } else {
  21.       this.sortColumn = columnIndex;
  22.       this.sortDirection = 1;
  23.     }
  24.    
  25.     // 排序行
  26.     this.rows.sort((a, b) => {
  27.       const aValue = a.cells[columnIndex].textContent;
  28.       const bValue = b.cells[columnIndex].textContent;
  29.       
  30.       // 尝试将值转换为数字
  31.       const aNum = parseFloat(aValue);
  32.       const bNum = parseFloat(bValue);
  33.       
  34.       let comparison;
  35.       if (!isNaN(aNum) && !isNaN(bNum)) {
  36.         comparison = aNum - bNum;
  37.       } else {
  38.         comparison = aValue.localeCompare(bValue);
  39.       }
  40.       
  41.       return comparison * this.sortDirection;
  42.     });
  43.    
  44.     // 重新插入排序后的行
  45.     this.rows.forEach(row => this.tbody.appendChild(row));
  46.    
  47.     // 更新表头样式
  48.     this.updateHeaderStyles(columnIndex);
  49.   }
  50.   
  51.   updateHeaderStyles(activeColumn) {
  52.     // 移除所有表头的样式
  53.     this.headers.forEach(header => {
  54.       header.classList.remove('sort-asc', 'sort-desc');
  55.     });
  56.    
  57.     // 为活动列添加样式
  58.     if (this.sortDirection === 1) {
  59.       this.headers[activeColumn].classList.add('sort-asc');
  60.     } else {
  61.       this.headers[activeColumn].classList.add('sort-desc');
  62.     }
  63.   }
  64. }
  65. // 使用示例
  66. const tableSorter = new TableSorter('data-table');
复制代码

7.2 树形菜单组件
  1. class TreeMenu {
  2.   constructor(containerId, data) {
  3.     this.container = document.getElementById(containerId);
  4.     this.data = data;
  5.     this.init();
  6.   }
  7.   
  8.   init() {
  9.     this.container.innerHTML = '';
  10.     this.renderTree(this.data, this.container);
  11.   }
  12.   
  13.   renderTree(items, parentElement) {
  14.     const ul = document.createElement('ul');
  15.    
  16.     items.forEach(item => {
  17.       const li = document.createElement('li');
  18.       
  19.       // 创建标题
  20.       const title = document.createElement('div');
  21.       title.className = 'tree-item-title';
  22.       title.textContent = item.title;
  23.       
  24.       // 如果有子项,添加展开/折叠图标
  25.       if (item.children && item.children.length > 0) {
  26.         const icon = document.createElement('span');
  27.         icon.className = 'tree-icon';
  28.         icon.textContent = '▶';
  29.         title.insertBefore(icon, title.firstChild);
  30.         
  31.         // 添加点击事件
  32.         title.addEventListener('click', () => {
  33.           icon.textContent = icon.textContent === '▶' ? '▼' : '▶';
  34.           const childrenContainer = li.querySelector('.tree-children');
  35.           if (childrenContainer) {
  36.             childrenContainer.style.display =
  37.               childrenContainer.style.display === 'none' ? 'block' : 'none';
  38.           }
  39.         });
  40.       }
  41.       
  42.       li.appendChild(title);
  43.       
  44.       // 如果有子项,递归渲染
  45.       if (item.children && item.children.length > 0) {
  46.         const childrenContainer = document.createElement('div');
  47.         childrenContainer.className = 'tree-children';
  48.         this.renderTree(item.children, childrenContainer);
  49.         li.appendChild(childrenContainer);
  50.       }
  51.       
  52.       ul.appendChild(li);
  53.     });
  54.    
  55.     parentElement.appendChild(ul);
  56.   }
  57. }
  58. // 使用示例
  59. const treeData = [
  60.   {
  61.     title: '项目1',
  62.     children: [
  63.       {
  64.         title: '子项目1.1',
  65.         children: [
  66.           { title: '子项目1.1.1' },
  67.           { title: '子项目1.1.2' }
  68.         ]
  69.       },
  70.       { title: '子项目1.2' }
  71.     ]
  72.   },
  73.   {
  74.     title: '项目2',
  75.     children: [
  76.       { title: '子项目2.1' },
  77.       { title: '子项目2.2' }
  78.     ]
  79.   }
  80. ];
  81. const treeMenu = new TreeMenu('tree-container', treeData);
复制代码

7.3 拖放排序列表
  1. class DragDropList {
  2.   constructor(listId) {
  3.     this.list = document.getElementById(listId);
  4.     this.items = Array.from(this.list.querySelectorAll('.draggable-item'));
  5.     this.draggedElement = null;
  6.    
  7.     this.init();
  8.   }
  9.   
  10.   init() {
  11.     // 为每个可拖拽项添加事件监听器
  12.     this.items.forEach(item => {
  13.       item.draggable = true;
  14.       
  15.       // 拖拽开始
  16.       item.addEventListener('dragstart', (e) => {
  17.         this.draggedElement = item;
  18.         item.classList.add('dragging');
  19.         e.dataTransfer.effectAllowed = 'move';
  20.         e.dataTransfer.setData('text/html', item.innerHTML);
  21.       });
  22.       
  23.       // 拖拽结束
  24.       item.addEventListener('dragend', () => {
  25.         item.classList.remove('dragging');
  26.       });
  27.       
  28.       // 拖拽经过
  29.       item.addEventListener('dragover', (e) => {
  30.         if (e.preventDefault) {
  31.           e.preventDefault(); // 允许放置
  32.         }
  33.         e.dataTransfer.dropEffect = 'move';
  34.         
  35.         // 确定插入位置
  36.         const afterElement = this.getDragAfterElement(this.list, e.clientY);
  37.         if (afterElement == null) {
  38.           this.list.appendChild(this.draggedElement);
  39.         } else {
  40.           this.list.insertBefore(this.draggedElement, afterElement);
  41.         }
  42.         
  43.         return false;
  44.       });
  45.     });
  46.   }
  47.   
  48.   getDragAfterElement(container, y) {
  49.     const draggableElements = [...container.querySelectorAll('.draggable-item:not(.dragging)')];
  50.    
  51.     return draggableElements.reduce((closest, child) => {
  52.       const box = child.getBoundingClientRect();
  53.       const offset = y - box.top - box.height / 2;
  54.       
  55.       if (offset < 0 && offset > closest.offset) {
  56.         return { offset: offset, element: child };
  57.       } else {
  58.         return closest;
  59.       }
  60.     }, { offset: Number.NEGATIVE_INFINITY }).element;
  61.   }
  62. }
  63. // 使用示例
  64. const dragDropList = new DragDropList('sortable-list');
复制代码

7.4 动态表单生成器
  1. class DynamicFormGenerator {
  2.   constructor(containerId, fields) {
  3.     this.container = document.getElementById(containerId);
  4.     this.fields = fields;
  5.     this.form = document.createElement('form');
  6.    
  7.     this.init();
  8.   }
  9.   
  10.   init() {
  11.     this.container.innerHTML = '';
  12.     this.container.appendChild(this.form);
  13.    
  14.     // 生成表单字段
  15.     this.fields.forEach(field => {
  16.       const formGroup = this.createFormGroup(field);
  17.       this.form.appendChild(formGroup);
  18.     });
  19.    
  20.     // 添加提交按钮
  21.     const submitButton = document.createElement('button');
  22.     submitButton.type = 'submit';
  23.     submitButton.textContent = '提交';
  24.     this.form.appendChild(submitButton);
  25.    
  26.     // 添加表单提交事件
  27.     this.form.addEventListener('submit', (e) => this.handleSubmit(e));
  28.   }
  29.   
  30.   createFormGroup(field) {
  31.     const formGroup = document.createElement('div');
  32.     formGroup.className = 'form-group';
  33.    
  34.     // 创建标签
  35.     const label = document.createElement('label');
  36.     label.textContent = field.label;
  37.     label.htmlFor = field.name;
  38.     formGroup.appendChild(label);
  39.    
  40.     // 根据字段类型创建输入元素
  41.     let input;
  42.     switch (field.type) {
  43.       case 'text':
  44.       case 'email':
  45.       case 'password':
  46.       case 'number':
  47.         input = document.createElement('input');
  48.         input.type = field.type;
  49.         input.name = field.name;
  50.         input.id = field.name;
  51.         if (field.required) input.required = true;
  52.         if (field.placeholder) input.placeholder = field.placeholder;
  53.         break;
  54.         
  55.       case 'textarea':
  56.         input = document.createElement('textarea');
  57.         input.name = field.name;
  58.         input.id = field.name;
  59.         if (field.required) input.required = true;
  60.         if (field.placeholder) input.placeholder = field.placeholder;
  61.         if (field.rows) input.rows = field.rows;
  62.         break;
  63.         
  64.       case 'select':
  65.         input = document.createElement('select');
  66.         input.name = field.name;
  67.         input.id = field.name;
  68.         if (field.required) input.required = true;
  69.         
  70.         // 添加选项
  71.         field.options.forEach(option => {
  72.           const optionElement = document.createElement('option');
  73.           optionElement.value = option.value;
  74.           optionElement.textContent = option.label;
  75.           input.appendChild(optionElement);
  76.         });
  77.         break;
  78.         
  79.       case 'checkbox':
  80.       case 'radio':
  81.         input = document.createElement('input');
  82.         input.type = field.type;
  83.         input.name = field.name;
  84.         input.id = field.name;
  85.         if (field.checked) input.checked = true;
  86.         
  87.         // 为复选框和单选按钮添加标签文本
  88.         const span = document.createElement('span');
  89.         span.textContent = field.label;
  90.         label.textContent = '';
  91.         label.appendChild(input);
  92.         label.appendChild(span);
  93.         break;
  94.     }
  95.    
  96.     if (input && field.type !== 'checkbox' && field.type !== 'radio') {
  97.       formGroup.appendChild(input);
  98.     }
  99.    
  100.     return formGroup;
  101.   }
  102.   
  103.   handleSubmit(e) {
  104.     e.preventDefault();
  105.    
  106.     // 收集表单数据
  107.     const formData = new FormData(this.form);
  108.     const data = {};
  109.    
  110.     for (let [key, value] of formData.entries()) {
  111.       data[key] = value;
  112.     }
  113.    
  114.     console.log('表单数据:', data);
  115.    
  116.     // 这里可以添加AJAX请求来提交表单数据
  117.     // fetch('/api/submit', {
  118.     //   method: 'POST',
  119.     //   headers: {
  120.     //     'Content-Type': 'application/json'
  121.     //   },
  122.     //   body: JSON.stringify(data)
  123.     // })
  124.     // .then(response => response.json())
  125.     // .then(result => {
  126.     //   console.log('提交成功:', result);
  127.     // })
  128.     // .catch(error => {
  129.     //   console.error('提交失败:', error);
  130.     // });
  131.   }
  132. }
  133. // 使用示例
  134. const formFields = [
  135.   { name: 'name', label: '姓名', type: 'text', required: true, placeholder: '请输入您的姓名' },
  136.   { name: 'email', label: '邮箱', type: 'email', required: true, placeholder: '请输入您的邮箱' },
  137.   { name: 'password', label: '密码', type: 'password', required: true },
  138.   { name: 'gender', label: '性别', type: 'radio', checked: true },
  139.   { name: 'message', label: '留言', type: 'textarea', placeholder: '请输入您的留言', rows: 4 },
  140.   {
  141.     name: 'country',
  142.     label: '国家',
  143.     type: 'select',
  144.     options: [
  145.       { value: 'cn', label: '中国' },
  146.       { value: 'us', label: '美国' },
  147.       { value: 'uk', label: '英国' }
  148.     ]
  149.   },
  150.   { name: 'terms', label: '我同意服务条款', type: 'checkbox', required: true }
  151. ];
  152. const formGenerator = new DynamicFormGenerator('form-container', formFields);
复制代码

总结

本文全面介绍了HTML DOM查询与遍历的相关知识,从基础选择器到高级树形结构操作,涵盖了前端开发中必备的DOM操作技能。我们学习了:

1. DOM基础概念,理解了DOM树形结构的组成和节点类型。
2. 基础选择器方法,如getElementById、getElementsByTagName、getElementsByClassName和getElementsByName。
3. 高级选择器方法,如querySelector、querySelectorAll、matches和closest,它们提供了更灵活的元素选择方式。
4. DOM节点关系与遍历,包括节点类型、节点关系属性、遍历DOM树的方法,以及TreeWalker和NodeIterator API。
5. 树形结构操作,包括创建、插入、替换、删除和克隆节点,以及操作元素属性。
6. 性能优化技巧,如减少DOM访问、使用文档片段、批量修改样式、使用事件委托、使用requestAnimationFrame和实现虚拟滚动。
7. 实际应用场景和示例,包括动态表格排序、树形菜单组件、拖放排序列表和动态表单生成器。

掌握这些DOM操作技能对于前端开发至关重要,它们不仅能够帮助我们构建交互性强的网页应用,还能通过优化技巧提升网页性能,提供更好的用户体验。随着前端技术的不断发展,DOM操作仍然是前端开发的基础,深入理解并熟练运用这些技能,将有助于我们在前端开发道路上走得更远。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

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

本版积分规则

关闭

站长推荐上一条 /1 下一条

手机版|联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.

>