目录
[一、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 事件传播有三个阶段:
-
捕获阶段:从最外层元素向内传播
-
目标阶段:到达事件目标
-
冒泡阶段:从事件目标向外传播
// 第三个参数为 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 遍历和查找
- 表单操作技巧
- 性能优化策略