JavaScript DOM操作

什么是DOM?

DOM(Document Object Model,文档对象模型)是JavaScript操作HTML和XML文档的编程接口。它将文档解析为一个由节点和对象组成的树结构,允许JavaScript通过这个树结构来访问、修改、添加和删除文档的内容、结构和样式。

DOM树结构

HTML文档被解析为一个树形结构,称为DOM树:

└── Document
    └── html
        ├── head
        │   ├── title
        │   ├── meta
        │   └── link
        └── body
            ├── h1
            ├── p
            └── div
                ├── ul
                │   ├── li
                │   └── li
                └── img

DOM节点类型

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

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

元素选择

1. 通过ID选择元素

使用getElementById()方法,返回匹配指定ID的第一个元素。

const element = document.getElementById('myElement');
console.log(element);

2. 通过类名选择元素

使用getElementsByClassName()方法,返回匹配指定类名的所有元素的集合。

const elements = document.getElementsByClassName('myClass');
console.log(elements); // HTMLCollection
console.log(elements[0]); // 第一个匹配元素

3. 通过标签名选择元素

使用getElementsByTagName()方法,返回匹配指定标签名的所有元素的集合。

const paragraphs = document.getElementsByTagName('p');
console.log(paragraphs.length); // 文档中段落的数量

4. 通过CSS选择器选择元素

querySelector()

返回匹配指定CSS选择器的第一个元素。

const element = document.querySelector('.myClass');
const nestedElement = document.querySelector('#container .item');

querySelectorAll()

返回匹配指定CSS选择器的所有元素的NodeList。

const elements = document.querySelectorAll('.myClass');
elements.forEach(element => {
  console.log(element);
});

元素操作

1. 内容操作

innerHTML

获取或设置元素的HTML内容。

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

// 获取HTML内容
console.log(element.innerHTML);

// 设置HTML内容
element.innerHTML = '<h2>新标题</h2><p>新内容</p>';

textContent

获取或设置元素的文本内容,忽略HTML标签。

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

// 获取文本内容
console.log(element.textContent);

// 设置文本内容
element.textContent = '这是新的文本内容';

innerText

获取或设置元素的可见文本内容,考虑样式和布局。

const element = document.getElementById('myElement');
console.log(element.innerText);
element.innerText = '这是可见的文本内容';

2. 属性操作

获取属性

const element = document.getElementById('myElement');
const value = element.getAttribute('data-value');
console.log(value);

设置属性

const element = document.getElementById('myElement');
element.setAttribute('data-value', 'new-value');
element.setAttribute('class', 'new-class');

移除属性

const element = document.getElementById('myElement');
element.removeAttribute('data-value');

检查属性是否存在

const element = document.getElementById('myElement');
const hasAttribute = element.hasAttribute('data-value');
console.log(hasAttribute);

3. 样式操作

内联样式

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

// 设置样式
element.style.color = 'red';
element.style.fontSize = '20px';
element.style.backgroundColor = '#f0f0f0';

// 获取样式
console.log(element.style.color); // red

类名操作

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

// 检查类名是否存在
console.log(element.classList.contains('myClass'));

// 添加类名
element.classList.add('newClass');
element.classList.add('class1', 'class2');

// 移除类名
element.classList.remove('oldClass');
element.classList.remove('class1', 'class2');

// 切换类名(存在则移除,不存在则添加)
element.classList.toggle('active');

// 替换类名
element.classList.replace('oldClass', 'newClass');

创建和插入元素

1. 创建元素

// 创建元素
const newElement = document.createElement('div');

// 设置属性
newElement.id = 'newElement';
newElement.className = 'new-class';
newElement.textContent = '这是新创建的元素';

2. 插入元素

appendChild()

在元素末尾添加子元素。

const parent = document.getElementById('parent');
const child = document.createElement('div');
child.textContent = '新子元素';
parent.appendChild(child);

prepend()

在元素开头添加子元素。

const parent = document.getElementById('parent');
const child = document.createElement('div');
child.textContent = '新子元素(开头)';
parent.prepend(child);

insertBefore()

在指定元素之前插入新元素。

const parent = document.getElementById('parent');
const referenceElement = document.getElementById('reference');
const newElement = document.createElement('div');
newElement.textContent = '插入的新元素';

parent.insertBefore(newElement, referenceElement);

after() 和 before()

在元素之后或之前插入兄弟元素。

const element = document.getElementById('myElement');
const newElement = document.createElement('div');
newElement.textContent = '兄弟元素';

// 在元素之后插入
element.after(newElement);

// 在元素之前插入
element.before(newElement);

删除和替换元素

1. 删除元素

const element = document.getElementById('myElement');
element.remove();

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

2. 替换元素

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

oldElement.replaceWith(newElement);

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

事件处理

1. 事件类型

常见的DOM事件类型包括:

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

2. 添加事件监听器

const button = document.getElementById('myButton');

// 添加点击事件监听器
button.addEventListener('click', function(event) {
  console.log('按钮被点击了!');
  console.log('事件对象:', event);
  console.log('事件目标:', event.target);
});

// 使用箭头函数
button.addEventListener('click', (event) => {
  console.log('按钮被点击了(箭头函数)!');
});

3. 移除事件监听器

const button = document.getElementById('myButton');

function handleClick() {
  console.log('按钮被点击了!');
}

// 添加事件监听器
button.addEventListener('click', handleClick);

// 移除事件监听器
button.removeEventListener('click', handleClick);

4. 事件冒泡和捕获

事件传播有三个阶段:

  1. 捕获阶段:事件从document向下传播到目标元素
  2. 目标阶段:事件到达目标元素
  3. 冒泡阶段:事件从目标元素向上传播到document
const parent = document.getElementById('parent');
const child = document.getElementById('child');

// 捕获阶段触发
parent.addEventListener('click', () => {
  console.log('父元素捕获');
}, true);

// 冒泡阶段触发
parent.addEventListener('click', () => {
  console.log('父元素冒泡');
}, false); // 默认为false

child.addEventListener('click', (event) => {
  console.log('子元素点击');
  // 阻止事件冒泡
  event.stopPropagation();
});

5. 事件委托

利用事件冒泡,将事件监听器添加到父元素,而不是每个子元素。

const list = document.getElementById('myList');

list.addEventListener('click', (event) => {
  if (event.target.tagName === 'LI') {
    console.log('列表项被点击了:', event.target.textContent);
    // 处理列表项点击事件
  }
});

DOM遍历

1. 父节点和子节点

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

// 获取父节点
const parent = element.parentNode;

// 获取父元素节点
const parentElement = element.parentElement;

// 获取所有子节点
const childNodes = element.childNodes;

// 获取所有子元素节点
const children = element.children;

// 获取第一个子节点
const firstChild = element.firstChild;

// 获取第一个子元素节点
const firstElementChild = element.firstElementChild;

// 获取最后一个子节点
const lastChild = element.lastChild;

// 获取最后一个子元素节点
const lastElementChild = element.lastElementChild;

2. 兄弟节点

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

// 获取前一个兄弟节点
const previousSibling = element.previousSibling;

// 获取前一个兄弟元素节点
const previousElementSibling = element.previousElementSibling;

// 获取后一个兄弟节点
const nextSibling = element.nextSibling;

// 获取后一个兄弟元素节点
const nextElementSibling = element.nextElementSibling;

DOM操作最佳实践

  1. 减少DOM操作次数:DOM操作是昂贵的,尽量减少操作次数
  2. 使用文档片段:对于大量DOM插入,使用DocumentFragment来减少重排和重绘
  3. 使用事件委托:对于多个相似元素,使用事件委托提高性能
  4. 避免频繁访问样式:将样式计算结果缓存
  5. 使用classList代替直接修改className:classList提供了更方便、更安全的CSS类操作方法
  6. 使用textContent代替innerHTML:当只需要操作文本时,textContent更安全、性能更好
  7. 及时移除事件监听器:避免内存泄漏
  8. 使用CSS过渡和动画:比JavaScript动画性能更好
  9. 使用requestAnimationFrame:对于JavaScript动画,使用requestAnimationFrame可以获得更平滑的效果
  10. 优化选择器:使用更高效的选择器,如ID选择器比复杂的CSS选择器更快

示例:创建一个待办事项列表

// HTML结构:<div id="todo-app"></div>

const todoApp = document.getElementById('todo-app');

// 创建应用结构
const appHTML = `
  <h1>待办事项</h1>
  <div class="todo-input">
    <input type="text" id="todo-input" placeholder="添加新的待办事项...">
    <button id="add-todo">添加</button>
  </div>
  <ul id="todo-list"></ul>
`;

todoApp.innerHTML = appHTML;

// 获取元素
const input = document.getElementById('todo-input');
const addButton = document.getElementById('add-todo');
const todoList = document.getElementById('todo-list');

// 添加待办事项的函数
function addTodo() {
  const text = input.value.trim();
  if (text === '') return;
  
  // 创建新的待办事项
  const todoItem = document.createElement('li');
  todoItem.className = 'todo-item';
  todoItem.innerHTML = `
    <span>${text}</span>
    <button class="delete-todo">删除</button>
  `;
  
  // 添加到列表
  todoList.appendChild(todoItem);
  
  // 清空输入
  input.value = '';
}

// 添加点击事件监听器
addButton.addEventListener('click', addTodo);

// 添加回车事件监听器
input.addEventListener('keypress', (event) => {
  if (event.key === 'Enter') {
    addTodo();
  }
});

// 使用事件委托处理删除事件
todoList.addEventListener('click', (event) => {
  if (event.target.classList.contains('delete-todo')) {
    const todoItem = event.target.closest('.todo-item');
    todoItem.remove();
  }
});

总结

DOM操作是JavaScript在浏览器中与HTML文档交互的核心方式。通过掌握DOM选择、操作、事件处理等技术,可以创建动态、交互式的网页应用。

主要内容包括:

  • DOM树结构和节点类型
  • 多种元素选择方法
  • 元素内容和样式的操作
  • 创建、插入、删除和替换元素
  • 事件处理机制,包括事件冒泡、捕获和委托
  • DOM遍历方法
  • DOM操作的最佳实践

在实际开发中,合理使用DOM操作技术,可以提高应用性能和用户体验。

练习

  1. 创建一个简单的网页,包含一个按钮和一个计数器,点击按钮计数器加1
  2. 实现一个图片轮播组件,包含前进、后退和自动播放功能
  3. 创建一个表单验证功能,实时检查输入内容的有效性
  4. 实现一个可拖拽的元素,使用鼠标事件
  5. 创建一个无限滚动列表,当滚动到页面底部时加载更多内容
  6. 实现一个模态框(Modal)组件,包含打开、关闭和遮罩层功能
« 上一篇 JavaScript错误处理 下一篇 » JavaScript事件循环