Appearance
JavaScript DOM 操作
DOM(文档对象模型)是HTML和XML文档的编程接口,JavaScript通过DOM API可以动态访问和更新文档内容、结构和样式。DOM将文档表示为节点树,每个节点代表文档的一部分。
DOM 基础概念
节点类型
DOM中的每个部分都是节点:
- Document节点:文档根节点
- Element节点:HTML标签元素
- Text节点:元素中的文本内容
- Attribute节点:元素的属性
- Comment节点:HTML注释
DOM树结构
html
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
对应的DOM树:
- Document
- html (Element)
- head (Element)
- title (Element)
- "Example" (Text)
- title (Element)
- body (Element)
- h1 (Element)
- "Hello World" (Text)
- h1 (Element)
- head (Element)
- html (Element)
获取元素
通过ID获取
javascript
// 获取单个元素,最高效的方法之一
const element = document.getElementById('myId');
// 注意:getElementById只能在document对象上调用,不能在其他元素上调用
通过类名获取
javascript
// 获取HTMLCollection(动态集合)
const elements = document.getElementsByClassName('myClass');
// 或者在特定元素下搜索
const container = document.getElementById('container');
const childElements = container.getElementsByClassName('myClass');
// HTMLCollection是动态的,DOM变化时会自动更新
console.log(elements.length); // 假设为3
const newElement = document.createElement('div');
newElement.className = 'myClass';
document.body.appendChild(newElement);
console.log(elements.length); // 现在为4
通过标签名获取
javascript
// 获取HTMLCollection
const divs = document.getElementsByTagName('div');
const allElements = document.getElementsByTagName('*'); // 获取所有元素
通过CSS选择器获取
javascript
// querySelector - 返回第一个匹配的元素
const header = document.querySelector('.header');
const firstButton = document.querySelector('button');
const specificElement = document.querySelector('div#myId.myClass');
// querySelectorAll - 返回NodeList(静态集合)
const buttons = document.querySelectorAll('button');
const links = document.querySelectorAll('a[href*="example"]');
// NodeList是静态的,DOM变化不会影响已获取的NodeList
const buttons1 = document.querySelectorAll('button');
console.log(buttons1.length); // 假设为3
const newButton = document.createElement('button');
document.body.appendChild(newButton);
const buttons2 = document.querySelectorAll('button');
console.log(buttons2.length); // 为4,但buttons1.length仍然是3
选择器语法
javascript
// ID选择器
document.querySelector('#myId');
// 类选择器
document.querySelector('.myClass');
// 属性选择器
document.querySelector('[data-id="123"]');
document.querySelector('input[type="text"]');
// 伪类选择器
document.querySelector('li:first-child');
document.querySelector('tr:nth-child(even)');
// 组合选择器
document.querySelector('div > p'); // 直接子元素
document.querySelector('div p'); // 后代元素
document.querySelector('h1 + p'); // 相邻兄弟元素
操作元素内容
textContent vs innerText vs innerHTML
javascript
const element = document.getElementById('myElement');
// textContent - 获取/设置元素及其后代的纯文本内容
element.textContent = 'New text content';
// 会转义HTML标签,更安全
// innerText - 获取/设置元素的渲染文本内容
element.innerText = 'Rendered text content';
// 考虑样式,只获取可见文本
// innerHTML - 获取/设置元素的HTML内容
element.innerHTML = '<strong>New HTML content</strong>';
// 可以插入HTML,但需要注意XSS安全问题
安全地插入HTML
javascript
// 防止XSS攻击的方法
function safeInsertHTML(element, htmlString) {
const tempDiv = document.createElement('div');
tempDiv.textContent = htmlString; // 先转义
element.innerHTML = tempDiv.innerHTML;
}
// 或使用DOMPurify等库进行净化
// const cleanHTML = DOMPurify.sanitize(dirtyHTML);
// element.innerHTML = cleanHTML;
操作元素属性
标准属性操作
javascript
const img = document.querySelector('img');
// getAttribute - 获取属性值
const src = img.getAttribute('src');
const alt = img.getAttribute('alt');
// setAttribute - 设置属性值
img.setAttribute('src', 'new-image.jpg');
img.setAttribute('alt', 'New description');
// removeAttribute - 移除属性
img.removeAttribute('title');
// 检查属性是否存在
if (img.hasAttribute('data-loaded')) {
console.log('Image is loaded');
}
布尔属性
javascript
const checkbox = document.querySelector('input[type="checkbox"]');
// 对于布尔属性,存在即为true
checkbox.setAttribute('checked', ''); // 或 'checked' 或 true
checkbox.removeAttribute('checked'); // 移除则为false
dataset API(自定义数据属性)
javascript
const element = document.querySelector('.myElement');
// 设置data属性
element.dataset.userId = '123';
element.dataset.userName = 'john';
// 获取data属性
const userId = element.dataset.userId; // '123'
// 对应HTML: <div data-user-id="123" data-user-name="john">
classList API
javascript
const element = document.querySelector('.myElement');
// 添加类
element.classList.add('new-class');
// 移除类
element.classList.remove('old-class');
// 切换类
element.classList.toggle('active');
// 替换类
element.classList.replace('old-class', 'new-class');
// 检查是否有类
if (element.classList.contains('active')) {
console.log('Element is active');
}
// 列出所有类
const classes = Array.from(element.classList); // 转换为数组
// 多个类操作(ES6)
element.classList.add('class1', 'class2', 'class3');
创建和操作元素
创建元素
javascript
// 创建新元素
const newDiv = document.createElement('div');
newDiv.textContent = 'This is a new div';
// 设置属性
newDiv.id = 'newDiv';
newDiv.className = 'container highlight';
// 创建带HTML内容的元素
const listItem = document.createElement('li');
listItem.innerHTML = '<strong>Item content</strong>';
// 创建文本节点
const textNode = document.createTextNode('Plain text');
添加元素到文档
javascript
const container = document.querySelector('#container');
// appendChild - 添加到末尾
container.appendChild(newDiv);
// insertBefore - 插入到指定位置
const referenceNode = container.firstChild;
container.insertBefore(newDiv, referenceNode);
// insertAdjacentElement - 灵活的位置插入
element.insertAdjacentElement('beforebegin', newElement); // 元素前面
element.insertAdjacentElement('afterbegin', newElement); // 元素内部开头
element.insertAdjacentElement('beforeend', newElement); // 元素内部结尾
element.insertAdjacentElement('afterend', newElement); // 元素后面
批量操作(文档片段)
javascript
// 使用文档片段提高性能
const fragment = document.createDocumentFragment();
const container = document.querySelector('#container');
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
// 一次性添加所有元素,只触发一次重排
container.appendChild(fragment);
DOM 遍历
父子关系遍历
javascript
const element = document.querySelector('.myElement');
// 父元素
const parent = element.parentNode;
const parentElement = element.parentElement; // 只返回元素节点
// 子元素
const children = element.children; // HTMLCollection,只包含元素节点
const childNodes = element.childNodes; // NodeList,包含所有节点类型
const firstChild = element.firstChild;
const lastChild = element.lastChild;
// 检查是否有子节点
if (element.hasChildNodes()) {
console.log('Element has children');
}
兄弟关系遍历
javascript
const element = document.querySelector('.myElement');
// 下一个兄弟元素
const nextSibling = element.nextSibling; // 可能是任何节点类型
const nextElementSibling = element.nextElementSibling; // 只是元素节点
// 上一个兄弟元素
const previousSibling = element.previousSibling;
const previousElementSibling = element.previousElementSibling;
查询祖先元素
javascript
const element = document.querySelector('.myElement');
// closest - 查找最近的匹配祖先元素
const form = element.closest('form');
const container = element.closest('.container');
// 检查元素是否匹配选择器
if (element.matches('.special-class')) {
console.log('Element has special class');
}
事件处理
事件监听器
javascript
const button = document.querySelector('#myButton');
// addEventListener - 推荐方法,可添加多个监听器
button.addEventListener('click', function(event) {
console.log('Button clicked!');
console.log(event.target); // 触发事件的元素
});
// 可以添加多个相同的事件监听器
button.addEventListener('click', function() {
console.log('Second handler');
});
// 移除事件监听器(需要引用同一函数)
function handleClick() {
console.log('Button clicked!');
}
button.addEventListener('click', handleClick);
// button.removeEventListener('click', handleClick);
事件对象
javascript
element.addEventListener('click', function(event) {
// 事件基本信息
console.log(event.type); // 'click'
console.log(event.target); // 触发事件的实际元素
console.log(event.currentTarget); // 当前处理事件的元素
// 阻止默认行为(如链接跳转、表单提交)
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
// 阻止事件的其他监听器执行
event.stopImmediatePropagation();
// 鼠标事件信息
console.log(event.clientX, event.clientY); // 相对于视口的坐标
console.log(event.button); // 鼠标按键(0=左键,1=中键,2=右键)
// 键盘事件信息
console.log(event.key); // 按键值
console.log(event.code); // 物理按键码
console.log(event.ctrlKey, event.shiftKey, event.altKey); // 修饰键状态
});
事件阶段
javascript
// 事件捕获阶段(从document到目标元素)
element.addEventListener('click', handler, true); // 第三个参数为true表示捕获阶段
// 事件冒泡阶段(从目标元素到document)
element.addEventListener('click', handler, false); // 或不传第三个参数
// 完整事件流:捕获 -> 目标 -> 冒泡
document.addEventListener('click', function(e) {
console.log('Document (capture)', e.eventPhase); // 1 (捕获阶段)
}, true);
element.addEventListener('click', function(e) {
console.log('Element (target)', e.eventPhase); // 2 (目标阶段)
}, true);
element.addEventListener('click', function(e) {
console.log('Element (bubble)', e.eventPhase); // 2 (目标阶段)
}, false);
document.addEventListener('click', function(e) {
console.log('Document (bubble)', e.eventPhase); // 3 (冒泡阶段)
}, false);
事件委托
利用事件冒泡机制,在父元素上处理子元素事件:
javascript
// 事件委托 - 在父元素上监听子元素事件
document.querySelector('#list').addEventListener('click', function(event) {
// 检查点击的是否是目标元素
if (event.target.matches('li')) {
console.log('List item clicked:', event.target.textContent);
}
// 或者检查更复杂的条件
if (event.target.tagName === 'BUTTON' && event.target.classList.contains('delete')) {
event.target.closest('li').remove();
}
});
// 动态添加的元素也能响应事件(因为事件在父元素上处理)
const newItem = document.createElement('li');
newItem.textContent = 'Dynamically added item';
document.querySelector('#list').appendChild(newItem);
// 点击新添加的项也能触发上面的事件处理器
表单操作
获取和设置表单值
javascript
const form = document.querySelector('form');
const usernameInput = document.querySelector('input[name="username"]');
const messageTextarea = document.querySelector('textarea[name="message"]');
const agreeCheckbox = document.querySelector('input[type="checkbox"]');
const genderRadio = document.querySelector('input[name="gender"]:checked');
const countrySelect = document.querySelector('select[name="country"]');
// 获取值
console.log(usernameInput.value);
console.log(agreeCheckbox.checked);
console.log(countrySelect.value);
// 设置值
usernameInput.value = 'new username';
agreeCheckbox.checked = true;
countrySelect.value = 'us';
表单事件
javascript
const form = document.querySelector('form');
const input = document.querySelector('input[name="username"]');
// 输入事件
input.addEventListener('input', function() {
console.log('Input changed:', this.value);
});
// 变化事件(失去焦点且值改变时触发)
input.addEventListener('change', function() {
console.log('Value changed:', this.value);
});
// 表单提交事件
form.addEventListener('submit', function(event) {
event.preventDefault(); // 阻止默认提交行为
// 获取表单数据
const formData = new FormData(form);
for (let [name, value] of formData) {
console.log(name, value);
}
// 或使用Object.fromEntries
const data = Object.fromEntries(formData);
console.log(data);
});
表单验证
javascript
function validateForm(form) {
const inputs = form.querySelectorAll('input[required], select[required], textarea[required]');
let isValid = true;
inputs.forEach(input => {
if (!input.value.trim()) {
input.classList.add('error');
isValid = false;
} else {
input.classList.remove('error');
}
});
return isValid;
}
// 实时验证
document.querySelectorAll('input').forEach(input => {
input.addEventListener('blur', function() {
if (this.hasAttribute('required') && !this.value.trim()) {
this.classList.add('error');
} else {
this.classList.remove('error');
}
});
});
样式操作
内联样式操作
javascript
const element = document.querySelector('.myElement');
// 设置单个样式属性
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.fontSize = '16px';
// 批量设置样式
element.style.cssText = 'color: red; background-color: blue; font-size: 16px';
// 设置多个样式(使用对象)
Object.assign(element.style, {
color: 'green',
backgroundColor: 'yellow',
fontSize: '18px'
});
CSS类操作
javascript
const element = document.querySelector('.myElement');
// 使用classList(推荐)
element.classList.add('active', 'highlighted');
element.classList.remove('inactive');
element.classList.toggle('visible');
element.classList.replace('old-class', 'new-class');
// 检查类
if (element.classList.contains('active')) {
// 元素有active类
}
CSS自定义属性(CSS变量)
javascript
const root = document.documentElement;
// 设置CSS变量
root.style.setProperty('--main-color', '#ff0000');
// 获取CSS变量
const mainColor = getComputedStyle(root).getPropertyValue('--main-color');
// 在元素上设置CSS变量
element.style.setProperty('--element-color', 'blue');
DOM 性能优化
减少重排和重绘
javascript
// 避免频繁的样式读写操作
const element = document.querySelector('.myElement');
// 不好的做法:频繁读写
element.style.left = '10px';
console.log(element.offsetLeft); // 触发重排
element.style.top = '10px';
console.log(element.offsetTop); // 再次触发重排
// 好的做法:批量操作
element.style.cssText = 'left: 10px; top: 10px;';
const position = element.getBoundingClientRect(); // 一次性获取多个值
console.log(position.left, position.top);
使用文档片段
javascript
// 批量添加元素时使用文档片段
const fragment = document.createDocumentFragment();
const container = document.querySelector('#container');
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
// 一次性添加,只触发一次重排
container.appendChild(fragment);
事件委托优化
javascript
// 不好的做法:为每个元素添加事件监听器
document.querySelectorAll('.button').forEach(button => {
button.addEventListener('click', handleClick);
});
// 好的做法:使用事件委托
document.addEventListener('click', function(event) {
if (event.target.matches('.button')) {
handleClick(event);
}
});
现代DOM API
MutationObserver(监听DOM变化)
javascript
// 创建观察器实例
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log('DOM changed:', mutation.type);
if (mutation.type === 'childList') {
console.log('Added:', mutation.addedNodes);
console.log('Removed:', mutation.removedNodes);
}
});
});
// 配置观察选项
const config = {
childList: true, // 观察子节点变化
subtree: true, // 观察所有后代节点
attributes: true, // 观察属性变化
attributeFilter: ['class', 'style'] // 只观察特定属性
};
// 开始观察目标节点
const targetNode = document.body;
observer.observe(targetNode, config);
// 停止观察
// observer.disconnect();
Intersection Observer(元素可见性检测)
javascript
// 检测元素是否进入视口(常用于懒加载)
const observer = new IntersectionObserver(function(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 元素进入视口
console.log('Element is visible');
// 加载图片或执行其他操作
}
});
}, {
threshold: 0.1 // 当10%的元素可见时触发
});
// 观察特定元素
observer.observe(document.querySelector('.lazy-image'));
这些现代API提供了更高效的方式来处理DOM操作,减少了手动监听和计算的工作量。