JavaScript 操作 DOM

目录

[一、DOM 基础概念](#一、DOM 基础概念)

[二、获取 DOM 元素](#二、获取 DOM 元素)

[2.1 通过 ID 获取元素](#2.1 通过 ID 获取元素)

[2.2 通过类名获取元素](#2.2 通过类名获取元素)

[2.3 通过标签名获取元素](#2.3 通过标签名获取元素)

[2.4 通过 CSS 选择器获取元素](#2.4 通过 CSS 选择器获取元素)

[2.5 获取特殊元素](#2.5 获取特殊元素)

[三、DOM 元素属性操作](#三、DOM 元素属性操作)

[3.1 获取和设置标准属性](#3.1 获取和设置标准属性)

[3.2 使用 getAttribute() 和 setAttribute()](#3.2 使用 getAttribute() 和 setAttribute())

[3.3 移除属性](#3.3 移除属性)

[3.4 检查属性是否存在](#3.4 检查属性是否存在)

[3.5 数据属性(data-*)](#3.5 数据属性(data-*))

四、修改元素内容

[4.1 innerHTML](#4.1 innerHTML)

[4.2 textContent](#4.2 textContent)

[4.3 outerHTML](#4.3 outerHTML)

五、操作元素样式

[5.1 直接操作 style 属性](#5.1 直接操作 style 属性)

[5.2 使用 classList 操作类名](#5.2 使用 classList 操作类名)

[5.3 获取计算样式](#5.3 获取计算样式)

[六、DOM 节点操作](#六、DOM 节点操作)

[6.1 创建节点](#6.1 创建节点)

[6.2 添加节点](#6.2 添加节点)

[6.3 插入节点](#6.3 插入节点)

[6.4 替换节点](#6.4 替换节点)

[6.5 移除节点](#6.5 移除节点)

[6.6 克隆节点](#6.6 克隆节点)

[七、DOM 事件处理](#七、DOM 事件处理)

[7.1 事件监听器的基本用法](#7.1 事件监听器的基本用法)

[7.2 常用事件类型](#7.2 常用事件类型)

[7.3 事件对象属性和方法](#7.3 事件对象属性和方法)

[7.4 事件冒泡和事件捕获](#7.4 事件冒泡和事件捕获)

[7.5 事件委托](#7.5 事件委托)

[八、DOM 遍历和查找](#八、DOM 遍历和查找)

[8.1 父节点和子节点](#8.1 父节点和子节点)

[8.2 兄弟节点](#8.2 兄弟节点)

[8.3 遍历所有子节点](#8.3 遍历所有子节点)

九、表单操作

[9.1 获取表单元素](#9.1 获取表单元素)

[9.2 获取和设置表单值](#9.2 获取和设置表单值)

[9.3 表单提交处理](#9.3 表单提交处理)

[十、DOM 性能优化](#十、DOM 性能优化)

[10.1 减少 DOM 操作次数](#10.1 减少 DOM 操作次数)

[10.2 避免频繁查询 DOM](#10.2 避免频繁查询 DOM)

[10.3 使用 CSS 类批量修改样式](#10.3 使用 CSS 类批量修改样式)

[10.4 避免 layout thrashing(布局抖动)](#10.4 避免 layout thrashing(布局抖动))

[十一、DOM 扩展方法](#十一、DOM 扩展方法)

十二、总结


一、DOM 基础概念

DOM(Document Object Model,文档对象模型)是 HTML 和 XML 文档的编程接口,它将文档解析为一个由节点和对象(包含属性和方法)组成的结构集合。JavaScript 可以通过 DOM 接口来操作网页的内容、结构和样式。

  • 文档:指整个 HTML 或 XML 页面
  • 对象:表示文档中的元素、属性、文本等
  • 模型:表示这些对象之间的关系,类似树状结构

DOM 树结构示意图:

复制代码
document
├── html
│   ├── head
│   │   ├── title
│   │   ├── meta
│   │   └── link
│   └── body
│       ├── h1
│       ├── p
│       └── div
└── ...

二、获取 DOM 元素

JavaScript 提供了多种方法来选择和获取 DOM 元素,以下是常用方法:

2.1 通过 ID 获取元素

复制代码
// getElementById() 返回具有指定 ID 的元素
const element = document.getElementById('myId');

注意:ID 在文档中应该是唯一的,该方法返回单个元素,如果找不到则返回 null

2.2 通过类名获取元素

复制代码
// getElementsByClassName() 返回具有指定类名的所有元素集合(HTMLCollection)
const elements = document.getElementsByClassName('myClass');

// 转换为数组以便使用数组方法
const elementsArray = Array.from(elements);
// 或
const elementsArray = [...elements];

2.3 通过标签名获取元素

复制代码
// getElementsByTagName() 返回具有指定标签名的所有元素集合(HTMLCollection)
const paragraphs = document.getElementsByTagName('p');
const divs = document.getElementsByTagName('div');

2.4 通过 CSS 选择器获取元素

复制代码
// querySelector() 返回匹配指定 CSS 选择器的第一个元素
const firstParagraph = document.querySelector('p');
const element = document.querySelector('#myId .myClass');

// querySelectorAll() 返回匹配指定 CSS 选择器的所有元素(NodeList)
const allParagraphs = document.querySelectorAll('p');
const elements = document.querySelectorAll('.myClass');

注意:querySelectorAll() 返回的 NodeList 是静态的,而 getElementsBy* 系列方法返回的是动态集合

2.5 获取特殊元素

复制代码
// 获取 html 元素
const htmlElement = document.documentElement;

// 获取 head 元素
const headElement = document.head;

// 获取 body 元素
const bodyElement = document.body;

// 获取所有表单元素
const forms = document.forms;

// 获取所有图像元素
const images = document.images;

三、DOM 元素属性操作

3.1 获取和设置标准属性

复制代码
const link = document.querySelector('a');

// 获取属性值
const href = link.href;
const target = link.target;

// 设置属性值
link.href = 'https://example.com';
link.target = '_blank';

3.2 使用 getAttribute() 和 setAttribute()

复制代码
const element = document.querySelector('div');

// 获取属性值
const id = element.getAttribute('id');
const classValue = element.getAttribute('class');

// 设置属性值
element.setAttribute('id', 'newId');
element.setAttribute('class', 'newClass');
element.setAttribute('data-custom', 'value'); // 设置自定义属性

3.3 移除属性

复制代码
const element = document.querySelector('div');
element.removeAttribute('class'); // 移除 class 属性

3.4 检查属性是否存在

复制代码
const element = document.querySelector('div');
if (element.hasAttribute('id')) {
  // 元素具有 id 属性
}

3.5 数据属性(data-*)

HTML5 允许自定义数据属性,前缀为 data-

复制代码
<div id="user" data-id="123" data-name="John" data-age="30"></div>

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

// 获取数据属性
const userId = user.dataset.id; // "123"
const userName = user.dataset.name; // "John"

// 设置数据属性
user.dataset.email = "john@example.com";
user.dataset.age = "31"; // 更新已有数据属性

// 移除数据属性
delete user.dataset.age;

四、修改元素内容

4.1 innerHTML

获取或设置元素的 HTML 内容,包括所有子元素:

复制代码
const container = document.getElementById('container');

// 获取 HTML 内容
const htmlContent = container.innerHTML;

// 设置 HTML 内容(会覆盖原有内容)
container.innerHTML = '<p>新的 HTML 内容</p>';

// 追加 HTML 内容
container.innerHTML += '<p>追加的内容</p>';

注意:使用 innerHTML 可能存在 XSS 安全风险,插入用户输入时要特别小心

4.2 textContent

获取或设置元素的文本内容,不包含 HTML 标签:

复制代码
const paragraph = document.querySelector('p');

// 获取文本内容
const text = paragraph.textContent;

// 设置文本内容
paragraph.textContent = '新的文本内容';

textContent 与 innerText 的区别:

  • textContent 会获取所有元素的内容,包括 <script><style>
  • innerText 只获取可见文本,受 CSS 样式影响
  • textContent 性能更好,推荐使用

4.3 outerHTML

获取或设置包括当前元素本身的 HTML 内容:

复制代码
const div = document.querySelector('div');

// 获取 outerHTML
const outerHtml = div.outerHTML; // <div>...</div>

// 设置 outerHTML(会替换当前元素)
div.outerHTML = '<section>新内容</section>';

五、操作元素样式

5.1 直接操作 style 属性

通过元素的 style 属性可以访问和修改内联样式:

复制代码
const element = document.querySelector('div');

// 设置样式
element.style.color = 'red';
element.style.backgroundColor = '#f0f0f0'; // 注意驼峰命名法
element.style.fontSize = '16px';
element.style.marginTop = '20px';

// 获取样式
const color = element.style.color;
const fontSize = element.style.fontSize;

注意:CSS 属性名在 JavaScript 中使用驼峰命名法(如 backgroundColor 对应 CSS 中的 background-color)

5.2 使用 classList 操作类名

复制代码
const element = document.querySelector('div');

// 添加类名
element.classList.add('active');
element.classList.add('highlight', 'rounded'); // 添加多个类名

// 移除类名
element.classList.remove('active');
element.classList.remove('highlight', 'rounded'); // 移除多个类名

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

// 检查是否包含类名
if (element.classList.contains('active')) {
  // 元素包含 active 类
}

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

5.3 获取计算样式

获取元素最终应用的所有样式(包括外部样式表、内部样式和内联样式):

复制代码
const element = document.querySelector('div');
const computedStyles = window.getComputedStyle(element);

// 获取计算后的样式值
const color = computedStyles.color;
const fontSize = computedStyles.fontSize;
const margin = computedStyles.margin;

六、DOM 节点操作

6.1 创建节点

复制代码
// 创建元素节点
const newDiv = document.createElement('div');
const newParagraph = document.createElement('p');

// 创建文本节点
const textNode = document.createTextNode('这是一段文本');

// 创建注释节点
const commentNode = document.createComment('这是一段注释');

6.2 添加节点

复制代码
const parent = document.getElementById('parent');
const child = document.createElement('div');
child.textContent = '新添加的元素';

// .appendChild() - 添加到子节点列表的末尾
parent.appendChild(child);

// .prepend() - 添加到子节点列表的开头(ES6+)
parent.prepend(child);

// .before() - 添加到当前节点前面(ES6+)
parent.before(child);

// .after() - 添加到当前节点后面(ES6+)
parent.after(child);

6.3 插入节点

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

// 在 referenceElement 前面插入 newElement
parent.insertBefore(newElement, referenceElement);

6.4 替换节点

复制代码
const parent = document.getElementById('parent');
const oldElement = document.getElementById('old');
const newElement = document.createElement('div');
newElement.textContent = '替换的元素';

// 替换节点
parent.replaceChild(newElement, oldElement);

6.5 移除节点

复制代码
const parent = document.getElementById('parent');
const child = document.getElementById('child');

// 方法一:通过父节点移除
parent.removeChild(child);

// 方法二:直接移除自身(ES6+)
child.remove();

6.6 克隆节点

复制代码
const original = document.getElementById('original');

// 浅克隆 - 只克隆元素本身,不克隆子节点
const shallowClone = original.cloneNode(false);

// 深克隆 - 克隆元素本身及所有子节点
const deepClone = original.cloneNode(true);

// 将克隆的节点添加到文档中
document.body.appendChild(deepClone);

七、DOM 事件处理

7.1 事件监听器的基本用法

复制代码
const button = document.querySelector('button');

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

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

// 移除事件监听器(需要引用同一个函数)
function handleClick() {
  console.log('按钮被点击了');
}

button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);

7.2 常用事件类型

  • 鼠标事件:click, dblclick, mousedown, mouseup, mousemove, mouseover, mouseout, mouseenter, mouseleave
  • 键盘事件:keydown, keyup, keypress
  • 表单事件:submit, reset, change, input, focus, blur
  • 文档事件:DOMContentLoaded, load, beforeunload, resize, scroll
  • 触摸事件:touchstart, touchmove, touchend(移动设备)

7.3 事件对象属性和方法

事件处理函数接收一个事件对象,包含事件的详细信息:

复制代码
element.addEventListener('click', (e) => {
  // 事件类型
  console.log(e.type); // "click"
  
  // 事件目标(触发事件的元素)
  console.log(e.target);
  
  // 当前元素(当前正在处理事件的元素)
  console.log(e.currentTarget);
  
  // 鼠标位置
  console.log('X坐标:', e.clientX, 'Y坐标:', e.clientY);
  
  // 阻止默认行为
  e.preventDefault();
  
  // 阻止事件冒泡
  e.stopPropagation();
});

7.4 事件冒泡和事件捕获

DOM 事件传播有三个阶段:

  1. 捕获阶段:从最外层元素向内传播

  2. 目标阶段:到达事件目标

  3. 冒泡阶段:从事件目标向外传播

    // 第三个参数为 true 表示在捕获阶段处理事件
    parent.addEventListener('click', () => {
    console.log('父元素捕获阶段');
    }, true);

    // 第三个参数为 false 或省略表示在冒泡阶段处理事件
    parent.addEventListener('click', () => {
    console.log('父元素冒泡阶段');
    }, false);

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

7.5 事件委托

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

复制代码
<ul id="list">
  <li>项目 1</li>
  <li>项目 2</li>
  <li>项目 3</li>
</ul>

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

// 事件委托:将事件监听器添加到父元素
list.addEventListener('click', (e) => {
  // 检查事件目标是否是 li 元素
  if (e.target.tagName === 'LI') {
    console.log('点击了项目:', e.target.textContent);
  }
});

事件委托的优势:

  • 减少事件监听器数量,提高性能
  • 动态添加的子元素也能响应事件,无需重新绑定

八、DOM 遍历和查找

8.1 父节点和子节点

复制代码
const element = document.querySelector('div');

// 获取父节点
const parent = element.parentNode;
const parentElement = element.parentElement; // 只返回元素节点

// 获取子节点
const childNodes = element.childNodes; // 所有子节点(包括文本、注释等)
const children = element.children; // 只包含元素子节点

// 获取第一个和最后一个子节点
const firstChild = element.firstChild;
const lastChild = element.lastChild;
const firstElementChild = element.firstElementChild; // 第一个元素子节点
const lastElementChild = element.lastElementChild; // 最后一个元素子节点

8.2 兄弟节点

复制代码
const element = document.querySelector('div');

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

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

8.3 遍历所有子节点

javascript 复制代码
function traverseChildren(element) {
  // 遍历所有子节点
  for (let i = 0; i < element.childNodes.length; i++) {
    const node = element.childNodes[i];
    console.log(node.nodeName, node.nodeType);
    
    // 如果是元素节点,递归遍历其子节点
    if (node.nodeType === Node.ELEMENT_NODE) {
      traverseChildren(node);
    }
  }
}

// 从 body 开始遍历整个文档
traverseChildren(document.body);

节点类型常量:

  • Node.ELEMENT_NODE (1):元素节点
  • Node.TEXT_NODE (3):文本节点
  • Node.COMMENT_NODE (8):注释节点
  • Node.DOCUMENT_NODE (9):文档节点

九、表单操作

9.1 获取表单元素

javascript 复制代码
// 通过 ID 获取表单
const form = document.getElementById('myForm');

// 通过 document.forms 获取
const formByName = document.forms['formName'];
const firstForm = document.forms[0]; // 通过索引获取

// 获取表单控件
const usernameInput = form.elements['username'];
const passwordInput = form.querySelector('input[type="password"]');

9.2 获取和设置表单值

javascript 复制代码
const usernameInput = document.getElementById('username');
const checkbox = document.getElementById('agree');
const select = document.getElementById('options');

// 获取值
const username = usernameInput.value;
const isAgreed = checkbox.checked; // 复选框和单选按钮使用 checked 属性
const selectedValue = select.value;

// 设置值
usernameInput.value = 'newUsername';
checkbox.checked = true;
select.value = 'option2';

9.3 表单提交处理

javascript 复制代码
const form = document.getElementById('myForm');

// 方式一:监听 form 的 submit 事件
form.addEventListener('submit', (e) => {
  e.preventDefault(); // 阻止表单默认提交行为
  
  // 处理表单数据
  const formData = new FormData(form);
  
  // 获取表单数据
  const username = formData.get('username');
  const password = formData.get('password');
  
  console.log('用户名:', username, '密码:', password);
  
  // 可以在这里发送 AJAX 请求
});

// 方式二:在 HTML 中使用 onsubmit 属性
// <form onsubmit="return handleSubmit(event)">
function handleSubmit(e) {
  e.preventDefault();
  // 处理表单提交
  return false; // 阻止默认提交
}

十、DOM 性能优化

10.1 减少 DOM 操作次数

DOM 操作代价高昂,应尽量减少操作次数:

javascript 复制代码
// 不推荐:频繁操作 DOM
const list = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
  const item = document.createElement('li');
  item.textContent = `项目 ${i}`;
  list.appendChild(item); // 每次循环都操作 DOM
}

// 推荐:使用文档片段减少 DOM 操作
const list = document.getElementById('list');
const fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
  const item = document.createElement('li');
  item.textContent = `项目 ${i}`;
  fragment.appendChild(item); // 操作文档片段,不影响 DOM
}

list.appendChild(fragment); // 一次性添加到 DOM

10.2 避免频繁查询 DOM

多次使用的 DOM 元素应缓存起来:

javascript 复制代码
// 不推荐:多次查询同一元素
for (let i = 0; i < 10; i++) {
  document.getElementById('myDiv').style.left = i * 10 + 'px';
}

// 推荐:缓存 DOM 元素引用
const myDiv = document.getElementById('myDiv');
for (let i = 0; i < 10; i++) {
  myDiv.style.left = i * 10 + 'px';
}

10.3 使用 CSS 类批量修改样式

复制代码
// 不推荐:逐个修改样式属性
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.fontSize = '16px';

// 推荐:使用 CSS 类
element.classList.add('highlight');
// 在 CSS 中定义 .highlight 类包含所有样式

10.4 避免 layout thrashing(布局抖动)

尽量避免在短时间内频繁读取和修改布局属性:

javascript 复制代码
// 不推荐:交替读取和修改布局属性
const elements = document.querySelectorAll('.box');
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = '100px';
  const height = elements[i].offsetHeight; // 触发重排
  elements[i].style.height = height + 'px';
}

// 推荐:先读取所有布局属性,再统一修改
const elements = document.querySelectorAll('.box');
const heights = [];

// 先读取所有需要的布局信息
for (let i = 0; i < elements.length; i++) {
  heights.push(elements[i].offsetHeight);
}

// 再统一修改布局
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = '100px';
  elements[i].style.height = heights[i] + 'px';
}

十一、DOM 扩展方法

可以封装一些常用的 DOM 操作方法,提高开发效率:

javascript 复制代码
// DOM 工具类
const DOMUtils = {
  // 根据选择器获取元素
  $: (selector) => document.querySelector(selector),
  
  // 根据选择器获取所有元素
  $$: (selector) => document.querySelectorAll(selector),
  
  // 创建元素并设置属性
  createElement: (tag, options = {}) => {
    const element = document.createElement(tag);
    Object.keys(options).forEach(key => {
      if (key === 'text') {
        element.textContent = options[key];
      } else if (key === 'html') {
        element.innerHTML = options[key];
      } else if (key === 'class') {
        element.className = options[key];
      } else {
        element.setAttribute(key, options[key]);
      }
    });
    return element;
  },
  
  // 为多个元素添加事件监听器
  on: (elements, event, handler) => {
    if (!Array.isArray(elements)) {
      elements = [elements];
    }
    elements.forEach(element => {
      element.addEventListener(event, handler);
    });
  },
  
  // 清空元素内容
  empty: (element) => {
    while (element.firstChild) {
      element.removeChild(element.firstChild);
    }
  }
};

// 使用示例
const div = DOMUtils.createElement('div', {
  class: 'container',
  id: 'main',
  text: 'Hello World'
});

DOMUtils.on(DOMUtils.$$('button'), 'click', (e) => {
  console.log('按钮被点击:', e.target);
});

十二、总结

JavaScript 操作 DOM 是前端开发的核心技能之一,本文详细介绍了:

  • DOM 基础概念和树结构
  • 获取 DOM 元素的多种方法
  • 元素属性和内容的操作
  • 样式修改技术
  • 节点的创建、添加、删除等操作
  • 事件处理机制和事件委托
  • DOM 遍历和查找
  • 表单操作技巧
  • 性能优化策略
相关推荐
牛角上的男孩2 小时前
apt update Ign and 404 Not Found
开发语言·数据库
再学一点就睡4 小时前
实现大文件上传全流程详解(补偿版本)
前端·javascript·面试
海绵宝宝汉堡包4 小时前
c# 项目 文件夹
开发语言·c#
小白要加油努力5 小时前
C++设计模式--策略模式与观察者模式
开发语言·c++·设计模式
你的人类朋友5 小时前
【Node&Vue】什么是ECMAScript?
前端·javascript·后端
小马学嵌入式~5 小时前
数据结构:队列 二叉树
c语言·开发语言·数据结构·算法
Slaughter信仰6 小时前
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第二章知识点问答(21题)
java·开发语言·jvm
shix .6 小时前
最近 | 黄淮教务 | 小工具合集
前端·javascript
焊锡与代码齐飞7 小时前
嵌入式第三十五课!!Linux下的网络编程
linux·运维·服务器·开发语言·网络·学习·算法