轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript

  • TypeScript

  • Node.js

  • Vue.js

  • 工程化

  • 浏览器与Web API

    • HTML基础-语义化标签与文档结构
    • HTML基础-文本与排版标签
    • HTML基础-列表与表格
    • HTML表单-Input类型详解
    • HTML表单-表单元素与验证
    • HTML交互-多媒体元素
    • HTML工程化-模板与组件化
    • HTML工程化-性能优化
    • CSS基础-选择器与优先级
    • CSS基础-盒模型与布局基础
    • CSS基础-单位与颜色系统
    • CSS基础-文本与字体
    • CSS基础-背景、列表与表格样式
    • CSS布局-Flexbox完全指南
    • CSS布局-Grid网格布局
    • CSS布局-响应式设计实践
    • CSS进阶-动画与过渡
    • CSS进阶-渐变与阴影效果
    • CSS进阶-Transform与3D变换
    • CSS进阶-滤镜与混合模式
    • 现代CSS-CSS预处理器对比
    • 现代CSS-CSS-in-JS方案
    • 现代CSS-原子化CSS与Tailwind
    • CSS工程化-架构与规范
    • CSS工程化-性能优化
    • CSS工程化-PostCSS实战指南
    • Web API基础-DOM操作完全指南
      • 一、DOM树结构与节点类型
        • 1.1 DOM树结构
        • 1.2 节点类型
        • 1.3 节点关系
      • 二、选择DOM元素
        • 2.1 传统选择方法
        • 2.2 现代选择器(推荐)
        • 2.3 HTMLCollection vs NodeList
        • 2.4 查找最近的祖先元素
      • 三、遍历DOM树
        • 3.1 父子遍历
        • 3.2 兄弟遍历
        • 3.3 TreeWalker
      • 四、读取和修改元素内容
        • 4.1 innerHTML vs textContent vs innerText
        • 4.2 outerHTML
        • 4.3 安全地插入HTML
      • 五、属性操作
        • 5.1 HTML属性操作
        • 5.2 data-* 自定义属性
        • 5.3 布尔属性
      • 六、类名操作
        • 6.1 className
        • 6.2 classList(推荐)
      • 七、样式操作
        • 7.1 内联样式(style)
        • 7.2 计算样式(getComputedStyle)
        • 7.3 样式最佳实践
      • 八、创建和插入元素
        • 8.1 创建元素
        • 8.2 插入元素
        • 8.3 DocumentFragment优化
        • 8.4 克隆元素
      • 九、删除和替换元素
        • 9.1 删除元素
        • 9.2 替换元素
      • 十、元素尺寸和位置
        • 10.1 元素尺寸
        • 10.2 元素位置
      • 十一、综合示例
      • 十二、性能优化
        • 12.1 减少DOM操作
        • 12.2 批量样式修改
        • 12.3 缓存DOM查询
        • 12.4 离线DOM操作
      • 十三、最佳实践
        • 13.1 选择器使用
        • 13.2 内容修改
        • 13.3 样式操作
        • 13.4 事件处理
      • 十四、总结
    • Web API基础-事件处理与委托
    • Web API基础-BOM与浏览器环境
    • Web API存储-客户端存储方案
    • Web API网络-HTTP请求详解
    • Web API网络-实时通信方案
    • Web API交互-用户体验增强
    • HTML&CSS历代版本新特性
  • 前端
  • 浏览器与Web API
轩辕李
2019-07-27
目录

Web API基础-DOM操作完全指南

# Web API基础-DOM操作完全指南

DOM(Document Object Model,文档对象模型)是Web开发的核心基础。浏览器将HTML文档解析为DOM树,JavaScript通过DOM API可以动态地访问、修改文档的结构、样式和内容。本文将系统地介绍DOM操作的方方面面。

# 一、DOM树结构与节点类型

# 1.1 DOM树结构

HTML文档被解析为树形结构:

<!DOCTYPE html>
<html>
  <head>
    <title>示例页面</title>
  </head>
  <body>
    <h1 id="title">欢迎</h1>
    <p class="intro">这是一段介绍文字。</p>
  </body>
</html>

对应的DOM树:

Document
└── html
    ├── head
    │   └── title
    │       └── "示例页面"
    └── body
        ├── h1#title
        │   └── "欢迎"
        └── p.intro
            └── "这是一段介绍文字。"

# 1.2 节点类型

DOM中的每个元素都是一个节点(Node),常见节点类型:

// 1. Element(元素节点)- nodeType: 1
const div = document.querySelector('div');
console.log(div.nodeType); // 1
console.log(div.nodeName); // "DIV"

// 2. Text(文本节点)- nodeType: 3
const text = div.firstChild;
console.log(text.nodeType); // 3
console.log(text.nodeName); // "#text"

// 3. Comment(注释节点)- nodeType: 8
// <!-- 这是注释 -->
const comment = document.createComment('这是注释');
console.log(comment.nodeType); // 8

// 4. Document(文档节点)- nodeType: 9
console.log(document.nodeType); // 9
console.log(document.nodeName); // "#document"

// 5. DocumentFragment(文档片段)- nodeType: 11
const fragment = document.createDocumentFragment();
console.log(fragment.nodeType); // 11

# 1.3 节点关系

const element = document.getElementById('container');

// 父节点
element.parentNode;
element.parentElement; // 只返回元素节点

// 子节点
element.childNodes; // 包含所有类型节点(含文本、注释)
element.children;   // 只包含元素节点(推荐)

element.firstChild; // 第一个子节点
element.lastChild;  // 最后一个子节点
element.firstElementChild; // 第一个子元素
element.lastElementChild;  // 最后一个子元素

// 兄弟节点
element.previousSibling; // 前一个兄弟节点
element.nextSibling;     // 后一个兄弟节点
element.previousElementSibling; // 前一个兄弟元素
element.nextElementSibling;     // 后一个兄弟元素

# 二、选择DOM元素

# 2.1 传统选择方法

// 通过ID选择(返回单个元素)
const title = document.getElementById('title');

// 通过类名选择(返回HTMLCollection)
const items = document.getElementsByClassName('item');

// 通过标签名选择(返回HTMLCollection)
const paragraphs = document.getElementsByTagName('p');

// 通过name属性选择(常用于表单)
const inputs = document.getElementsByName('username');

# 2.2 现代选择器(推荐)

// querySelector(返回第一个匹配元素)
const title = document.querySelector('#title');
const firstItem = document.querySelector('.item');
const firstDiv = document.querySelector('div');

// querySelectorAll(返回所有匹配元素,NodeList)
const allItems = document.querySelectorAll('.item');
const allDivs = document.querySelectorAll('div');

// 支持复杂CSS选择器
const activeLink = document.querySelector('nav a.active');
const externalLinks = document.querySelectorAll('a[href^="http"]');
const firstParagraph = document.querySelector('article > p:first-child');
const evenItems = document.querySelectorAll('li:nth-child(even)');

# 2.3 HTMLCollection vs NodeList

// HTMLCollection(动态集合)
const divs1 = document.getElementsByTagName('div');
console.log(divs1.length); // 假设为 3

// 新增元素后,HTMLCollection自动更新
document.body.appendChild(document.createElement('div'));
console.log(divs1.length); // 变为 4

// NodeList(querySelectorAll返回静态集合)
const divs2 = document.querySelectorAll('div');
console.log(divs2.length); // 假设为 4

// 新增元素后,NodeList不会自动更新
document.body.appendChild(document.createElement('div'));
console.log(divs2.length); // 仍为 4

// NodeList转数组
const divsArray = Array.from(divs2);
const divsArray2 = [...divs2];

# 2.4 查找最近的祖先元素

const element = document.querySelector('.child');

// 查找最近的匹配选择器的祖先元素
const container = element.closest('.container');
const form = element.closest('form');

// 检查元素是否匹配选择器
if (element.matches('.active')) {
  console.log('元素包含active类');
}

# 三、遍历DOM树

# 3.1 父子遍历

const parent = document.getElementById('parent');

// 遍历所有子元素
for (const child of parent.children) {
  console.log(child.tagName);
}

// 递归遍历所有后代元素
function traverseDOM(element, callback) {
  callback(element);
  
  for (const child of element.children) {
    traverseDOM(child, callback);
  }
}

traverseDOM(document.body, (el) => {
  console.log(el.tagName);
});

# 3.2 兄弟遍历

const element = document.querySelector('.current');

// 遍历所有后续兄弟元素
let sibling = element.nextElementSibling;
while (sibling) {
  console.log(sibling);
  sibling = sibling.nextElementSibling;
}

// 遍历所有前置兄弟元素
let prevSibling = element.previousElementSibling;
while (prevSibling) {
  console.log(prevSibling);
  prevSibling = prevSibling.previousElementSibling;
}

# 3.3 TreeWalker

TreeWalker提供更灵活的DOM遍历方式:

const walker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT, // 只显示元素节点
  {
    acceptNode(node) {
      // 只接受class包含'item'的元素
      return node.classList.contains('item') 
        ? NodeFilter.FILTER_ACCEPT 
        : NodeFilter.FILTER_SKIP;
    }
  }
);

let node;
while (node = walker.nextNode()) {
  console.log(node);
}

# 四、读取和修改元素内容

# 4.1 innerHTML vs textContent vs innerText

const div = document.querySelector('div');

// innerHTML:HTML字符串
div.innerHTML = '<strong>加粗文字</strong>';
console.log(div.innerHTML); // "<strong>加粗文字</strong>"

// textContent:纯文本(推荐)
div.textContent = '<strong>加粗文字</strong>';
console.log(div.textContent); // "<strong>加粗文字</strong>"(显示为文本)

// innerText:考虑样式的文本(会触发重排,性能较差)
div.innerText = '文本内容';

区别对比:

<div id="test" style="display: none;">
  Hello <span>World</span>
</div>

<script>
  const div = document.getElementById('test');
  
  console.log(div.innerHTML);     // "Hello <span>World</span>"
  console.log(div.textContent);   // "Hello World"
  console.log(div.innerText);     // ""(因为元素被隐藏)
</script>

# 4.2 outerHTML

const div = document.querySelector('div');

// outerHTML:包含元素本身
console.log(div.outerHTML); // "<div>内容</div>"

// 替换整个元素
div.outerHTML = '<p>新段落</p>'; // div被p替换

# 4.3 安全地插入HTML

// ✗ 避免:直接使用innerHTML(XSS风险)
const userInput = '<img src=x onerror="alert(1)">';
div.innerHTML = userInput; // 危险!

// ✓ 推荐:使用textContent
div.textContent = userInput; // 安全,作为文本显示

// ✓ 推荐:使用DOMPurify库清理HTML
// div.innerHTML = DOMPurify.sanitize(userInput);

# 五、属性操作

# 5.1 HTML属性操作

const link = document.querySelector('a');

// getAttribute / setAttribute
link.getAttribute('href');           // 获取属性
link.setAttribute('href', '/new');   // 设置属性
link.hasAttribute('target');         // 检查属性是否存在
link.removeAttribute('target');      // 删除属性

// 直接属性访问(推荐)
link.href = 'https://example.com';
link.target = '_blank';
console.log(link.href);

# 5.2 data-* 自定义属性

<div id="user" 
     data-id="123" 
     data-name="张三"
     data-role="admin"
     data-created-at="2024-01-15">
</div>

<script>
  const user = document.getElementById('user');
  
  // dataset API(推荐)
  console.log(user.dataset.id);        // "123"
  console.log(user.dataset.name);      // "张三"
  console.log(user.dataset.role);      // "admin"
  console.log(user.dataset.createdAt); // "2024-01-15"(驼峰转换)
  
  // 设置data属性
  user.dataset.status = 'active';
  user.dataset.lastLogin = '2024-01-20';
  
  // 删除data属性
  delete user.dataset.role;
  
  // getAttribute方式
  console.log(user.getAttribute('data-id')); // "123"
</script>

# 5.3 布尔属性

const checkbox = document.querySelector('input[type="checkbox"]');

// 布尔属性(checked、disabled、selected等)
checkbox.checked = true;
checkbox.disabled = false;

// 通过setAttribute设置布尔属性
checkbox.setAttribute('checked', ''); // 存在即为true
checkbox.removeAttribute('checked');  // 删除即为false

# 六、类名操作

# 6.1 className

const div = document.querySelector('div');

// className(字符串)
div.className = 'btn btn-primary active';
console.log(div.className); // "btn btn-primary active"

// 添加类(需要手动处理)
div.className += ' new-class';

# 6.2 classList(推荐)

const div = document.querySelector('div');

// add:添加类
div.classList.add('active');
div.classList.add('btn', 'btn-primary'); // 可添加多个

// remove:删除类
div.classList.remove('active');
div.classList.remove('btn', 'btn-primary');

// toggle:切换类
div.classList.toggle('active'); // 有则删,无则加
div.classList.toggle('active', true);  // 强制添加
div.classList.toggle('active', false); // 强制删除

// contains:检查类是否存在
if (div.classList.contains('active')) {
  console.log('包含active类');
}

// replace:替换类
div.classList.replace('old-class', 'new-class');

// 遍历类名
for (const className of div.classList) {
  console.log(className);
}

# 七、样式操作

# 7.1 内联样式(style)

const div = document.querySelector('div');

// 设置样式
div.style.color = 'red';
div.style.backgroundColor = 'blue'; // CSS属性名转驼峰
div.style.fontSize = '16px';

// 读取内联样式
console.log(div.style.color); // "red"

// CSS属性名
div.style['background-color'] = 'blue';

// cssText:批量设置
div.style.cssText = 'color: red; background: blue; font-size: 16px;';

// 删除样式
div.style.color = '';
div.style.removeProperty('background-color');

# 7.2 计算样式(getComputedStyle)

const div = document.querySelector('div');

// 获取计算后的样式(包括CSS文件中的样式)
const styles = window.getComputedStyle(div);

console.log(styles.color);           // 实际渲染的颜色
console.log(styles.fontSize);        // 实际字体大小
console.log(styles.display);         // 实际display值
console.log(styles.width);           // 实际宽度(像素值)

// 获取伪元素样式
const beforeStyles = window.getComputedStyle(div, '::before');
console.log(beforeStyles.content);

# 7.3 样式最佳实践

// ✗ 避免:频繁操作style
for (let i = 0; i < 100; i++) {
  div.style.width = i + 'px'; // 触发多次重排
}

// ✓ 推荐:通过classList切换预定义样式
div.classList.add('expanded');

// ✓ 推荐:批量修改使用cssText
div.style.cssText = 'width: 100px; height: 100px; background: red;';

# 八、创建和插入元素

# 8.1 创建元素

// 创建元素
const div = document.createElement('div');
div.className = 'container';
div.textContent = '内容';

// 创建文本节点
const text = document.createTextNode('文本内容');

// 创建注释
const comment = document.createComment('这是注释');

// 创建文档片段(性能优化)
const fragment = document.createDocumentFragment();

# 8.2 插入元素

const parent = document.getElementById('parent');
const newElement = document.createElement('div');

// appendChild:添加到末尾
parent.appendChild(newElement);

// insertBefore:插入到指定元素前
const reference = parent.firstElementChild;
parent.insertBefore(newElement, reference);

// append:现代方法,可插入多个节点/文本
parent.append(newElement, '文本', anotherElement);

// prepend:插入到开头
parent.prepend(newElement);

// before:插入到元素前
reference.before(newElement);

// after:插入到元素后
reference.after(newElement);

# 8.3 DocumentFragment优化

// ✗ 避免:多次插入触发重排
const ul = document.querySelector('ul');
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  ul.appendChild(li); // 触发100次重排
}

// ✓ 推荐:使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
ul.appendChild(fragment); // 只触发1次重排

# 8.4 克隆元素

const original = document.querySelector('.original');

// 浅克隆(不包含子元素)
const shallowClone = original.cloneNode(false);

// 深克隆(包含所有子元素)
const deepClone = original.cloneNode(true);

// 注意:事件监听器不会被克隆

# 九、删除和替换元素

# 9.1 删除元素

const element = document.querySelector('.to-remove');

// remove:删除自己(现代方法)
element.remove();

// removeChild:通过父元素删除
const parent = element.parentNode;
parent.removeChild(element);

// 删除所有子元素
const container = document.querySelector('.container');
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

// 现代方法:replaceChildren
container.replaceChildren(); // 清空所有子元素

# 9.2 替换元素

const oldElement = document.querySelector('.old');
const newElement = document.createElement('div');
newElement.textContent = '新元素';

// replaceWith:替换自己
oldElement.replaceWith(newElement);

// replaceChild:通过父元素替换
const parent = oldElement.parentNode;
parent.replaceChild(newElement, oldElement);

// replaceChildren:替换所有子元素
const container = document.querySelector('.container');
container.replaceChildren(newElement); // 清空并添加新元素

# 十、元素尺寸和位置

# 10.1 元素尺寸

const element = document.querySelector('.box');

// offsetWidth/offsetHeight:包含边框和滚动条
console.log(element.offsetWidth);  // width + padding + border
console.log(element.offsetHeight); // height + padding + border

// clientWidth/clientHeight:不含边框和滚动条
console.log(element.clientWidth);  // width + padding
console.log(element.clientHeight); // height + padding

// scrollWidth/scrollHeight:内容实际宽高(含溢出部分)
console.log(element.scrollWidth);
console.log(element.scrollHeight);

// getBoundingClientRect:相对视口的位置和尺寸
const rect = element.getBoundingClientRect();
console.log(rect.top);    // 距离视口顶部
console.log(rect.left);   // 距离视口左侧
console.log(rect.width);  // 宽度
console.log(rect.height); // 高度
console.log(rect.right);  // 右边缘位置
console.log(rect.bottom); // 底边缘位置

# 10.2 元素位置

const element = document.querySelector('.box');

// offsetLeft/offsetTop:相对于offsetParent的位置
console.log(element.offsetLeft);
console.log(element.offsetTop);
console.log(element.offsetParent); // 最近的定位祖先元素

// scrollLeft/scrollTop:滚动距离
console.log(element.scrollLeft);  // 水平滚动距离
console.log(element.scrollTop);   // 垂直滚动距离

// 设置滚动位置
element.scrollLeft = 100;
element.scrollTop = 200;

// scrollTo:平滑滚动
element.scrollTo({
  top: 500,
  left: 0,
  behavior: 'smooth'
});

// scrollIntoView:滚动到可见
element.scrollIntoView({ behavior: 'smooth', block: 'center' });

# 十一、综合示例

<html>
  <div id="dom-demo-t3u4v">
    <h3>DOM操作综合示例</h3>
    
    <div class="controls-t3u4v">
      <input type="text" id="task-input-t3u4v" placeholder="输入任务内容">
      <button id="add-btn-t3u4v">添加任务</button>
      <button id="clear-btn-t3u4v">清空全部</button>
    </div>
    
    <div class="filters-t3u4v">
      <button class="filter-btn-t3u4v active-t3u4v" data-filter="all">全部</button>
      <button class="filter-btn-t3u4v" data-filter="active">未完成</button>
      <button class="filter-btn-t3u4v" data-filter="completed">已完成</button>
    </div>
    
    <ul id="task-list-t3u4v" class="task-list-t3u4v"></ul>
    
    <div class="stats-t3u4v">
      <span>总计: <strong id="total-t3u4v">0</strong></span>
      <span>未完成: <strong id="active-t3u4v">0</strong></span>
      <span>已完成: <strong id="completed-t3u4v">0</strong></span>
    </div>
  </div>
</html>

<style>
  #dom-demo-t3u4v {
    font-family: sans-serif;
    max-width: 600px;
    margin: 0 auto;
  }
  
  .controls-t3u4v {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
  }
  
  #task-input-t3u4v {
    flex: 1;
    padding: 10px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
  }
  
  #add-btn-t3u4v,
  #clear-btn-t3u4v {
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    transition: background 0.2s;
  }
  
  #add-btn-t3u4v {
    background: #2196F3;
    color: white;
  }
  
  #add-btn-t3u4v:hover {
    background: #1976D2;
  }
  
  #clear-btn-t3u4v {
    background: #f44336;
    color: white;
  }
  
  #clear-btn-t3u4v:hover {
    background: #d32f2f;
  }
  
  .filters-t3u4v {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
  }
  
  .filter-btn-t3u4v {
    padding: 8px 16px;
    border: 1px solid #ddd;
    background: white;
    border-radius: 4px;
    cursor: pointer;
    font-size: 13px;
    transition: all 0.2s;
  }
  
  .filter-btn-t3u4v:hover {
    background: #f5f5f5;
  }
  
  .filter-btn-t3u4v.active-t3u4v {
    background: #2196F3;
    color: white;
    border-color: #2196F3;
  }
  
  .task-list-t3u4v {
    list-style: none;
    padding: 0;
    margin: 0 0 16px 0;
    min-height: 200px;
    background: #f9f9f9;
    border-radius: 4px;
    padding: 12px;
  }
  
  .task-item-t3u4v {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    background: white;
    border-radius: 4px;
    margin-bottom: 8px;
    border: 1px solid #e0e0e0;
    transition: all 0.2s;
  }
  
  .task-item-t3u4v:hover {
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  
  .task-item-t3u4v.completed-t3u4v {
    opacity: 0.6;
  }
  
  .task-item-t3u4v.completed-t3u4v .task-text-t3u4v {
    text-decoration: line-through;
    color: #999;
  }
  
  .task-checkbox-t3u4v {
    cursor: pointer;
    width: 18px;
    height: 18px;
  }
  
  .task-text-t3u4v {
    flex: 1;
    font-size: 14px;
    color: #333;
  }
  
  .task-delete-t3u4v {
    padding: 6px 12px;
    background: #f44336;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
    transition: background 0.2s;
  }
  
  .task-delete-t3u4v:hover {
    background: #d32f2f;
  }
  
  .stats-t3u4v {
    display: flex;
    justify-content: space-around;
    padding: 12px;
    background: #f5f5f5;
    border-radius: 4px;
    font-size: 14px;
    color: #666;
  }
  
  .stats-t3u4v strong {
    color: #2196F3;
  }
  
  .empty-message-t3u4v {
    text-align: center;
    color: #999;
    padding: 40px 20px;
    font-size: 14px;
  }
</style>

<script>
  const taskInput = document.getElementById('task-input-t3u4v');
  const addBtn = document.getElementById('add-btn-t3u4v');
  const clearBtn = document.getElementById('clear-btn-t3u4v');
  const taskList = document.getElementById('task-list-t3u4v');
  const filterBtns = document.querySelectorAll('.filter-btn-t3u4v');
  
  let currentFilter = 'all';
  let tasks = [];
  
  function createTaskElement(task) {
    const li = document.createElement('li');
    li.className = 'task-item-t3u4v';
    li.dataset.id = task.id;
    
    if (task.completed) {
      li.classList.add('completed-t3u4v');
    }
    
    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.className = 'task-checkbox-t3u4v';
    checkbox.checked = task.completed;
    
    const span = document.createElement('span');
    span.className = 'task-text-t3u4v';
    span.textContent = task.text;
    
    const deleteBtn = document.createElement('button');
    deleteBtn.className = 'task-delete-t3u4v';
    deleteBtn.textContent = '删除';
    
    li.append(checkbox, span, deleteBtn);
    
    return li;
  }
  
  function renderTasks() {
    taskList.innerHTML = '';
    
    const filteredTasks = tasks.filter(task => {
      if (currentFilter === 'active') return !task.completed;
      if (currentFilter === 'completed') return task.completed;
      return true;
    });
    
    if (filteredTasks.length === 0) {
      const emptyMsg = document.createElement('div');
      emptyMsg.className = 'empty-message-t3u4v';
      emptyMsg.textContent = '暂无任务';
      taskList.appendChild(emptyMsg);
    } else {
      const fragment = document.createDocumentFragment();
      
      filteredTasks.forEach(task => {
        fragment.appendChild(createTaskElement(task));
      });
      
      taskList.appendChild(fragment);
    }
    
    updateStats();
  }
  
  function updateStats() {
    const total = tasks.length;
    const active = tasks.filter(t => !t.completed).length;
    const completed = tasks.filter(t => t.completed).length;
    
    document.getElementById('total-t3u4v').textContent = total;
    document.getElementById('active-t3u4v').textContent = active;
    document.getElementById('completed-t3u4v').textContent = completed;
  }
  
  function addTask() {
    const text = taskInput.value.trim();
    
    if (!text) {
      alert('请输入任务内容');
      return;
    }
    
    const task = {
      id: Date.now(),
      text: text,
      completed: false
    };
    
    tasks.push(task);
    taskInput.value = '';
    renderTasks();
  }
  
  addBtn.addEventListener('click', addTask);
  
  taskInput.addEventListener('keypress', (e) => {
    if (e.key === 'Enter') {
      addTask();
    }
  });
  
  clearBtn.addEventListener('click', () => {
    if (tasks.length === 0) return;
    
    if (confirm('确定要清空所有任务吗?')) {
      tasks = [];
      renderTasks();
    }
  });
  
  taskList.addEventListener('click', (e) => {
    const taskItem = e.target.closest('.task-item-t3u4v');
    if (!taskItem) return;
    
    const taskId = Number(taskItem.dataset.id);
    const task = tasks.find(t => t.id === taskId);
    
    if (e.target.classList.contains('task-checkbox-t3u4v')) {
      task.completed = e.target.checked;
      taskItem.classList.toggle('completed-t3u4v', task.completed);
      updateStats();
    } else if (e.target.classList.contains('task-delete-t3u4v')) {
      tasks = tasks.filter(t => t.id !== taskId);
      renderTasks();
    }
  });
  
  filterBtns.forEach(btn => {
    btn.addEventListener('click', () => {
      filterBtns.forEach(b => b.classList.remove('active-t3u4v'));
      btn.classList.add('active-t3u4v');
      
      currentFilter = btn.dataset.filter;
      renderTasks();
    });
  });
  
  renderTasks();
</script>

# 十二、性能优化

# 12.1 减少DOM操作

// ✗ 避免:频繁读写DOM
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div');
  div.textContent = i;
  container.appendChild(div); // 触发100次重排
}

// ✓ 推荐:使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div');
  div.textContent = i;
  fragment.appendChild(div);
}
container.appendChild(fragment); // 只触发1次重排

# 12.2 批量样式修改

// ✗ 避免:逐个修改样式
element.style.width = '100px';   // 重排
element.style.height = '100px';  // 重排
element.style.background = 'red'; // 重排

// ✓ 推荐:批量修改
element.style.cssText = 'width: 100px; height: 100px; background: red;';

// ✓ 推荐:使用类名
element.classList.add('box-style');

# 12.3 缓存DOM查询

// ✗ 避免:重复查询
for (let i = 0; i < 100; i++) {
  document.querySelector('.container').appendChild(div);
}

// ✓ 推荐:缓存查询结果
const container = document.querySelector('.container');
for (let i = 0; i < 100; i++) {
  container.appendChild(div);
}

# 12.4 离线DOM操作

// 先从文档中移除,操作完再插入
const container = document.querySelector('.container');
const parent = container.parentNode;
parent.removeChild(container);

// 进行大量DOM操作
for (let i = 0; i < 1000; i++) {
  // ...操作container
}

parent.appendChild(container);

# 十三、最佳实践

# 13.1 选择器使用

  1. 优先使用getElementById(最快)
  2. 其次使用querySelector/querySelectorAll
  3. 避免过于复杂的选择器
  4. 缩小查询范围:element.querySelector()而不是document.querySelector()

# 13.2 内容修改

  1. 使用textContent而不是innerText(性能更好)
  2. 避免直接使用innerHTML插入用户输入(XSS风险)
  3. 大量内容使用DocumentFragment

# 13.3 样式操作

  1. 优先使用classList而不是className
  2. 批量样式修改使用cssText或类名切换
  3. 读取样式使用getComputedStyle

# 13.4 事件处理

  1. 使用事件委托减少事件监听器数量
  2. 及时移除不需要的事件监听器
  3. 避免在循环中添加事件监听器

# 十四、总结

DOM操作是Web开发的基础技能,掌握以下要点:

  1. 理解DOM树结构:节点类型、节点关系
  2. 高效选择元素:使用现代选择器API
  3. 灵活操作元素:内容、属性、样式、类名
  4. 动态创建元素:createElement、DocumentFragment
  5. 性能优化:减少重排重绘、批量操作、缓存查询

通过合理使用DOM API,可以实现丰富的交互效果和动态内容更新。

祝你变得更强!

编辑 (opens new window)
#Web API#DOM#JavaScript
上次更新: 2025/11/28
CSS工程化-PostCSS实战指南
Web API基础-事件处理与委托

← CSS工程化-PostCSS实战指南 Web API基础-事件处理与委托→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式