Appearance
DOM操作
DOM(Document Object Model,文档对象模型)是 JavaScript 操作网页的接口。通过 DOM,JavaScript 可以访问和修改网页的内容、结构和样式。本章将详细介绍 DOM 操作的各种方法。
DOM 简介
什么是 DOM
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>DOM 示例</title>
</head>
<body>
<div id="container">
<h1 class="title">标题</h1>
<p>段落内容</p>
<ul>
<li>列表项 1</li>
<li>列表项 2</li>
</ul>
</div>
<script>
// DOM 将 HTML 文档表示为一个树形结构
// 每个HTML元素都是树中的一个节点
// document 对象是整个文档的入口
console.log(document); // 整个 HTML 文档
console.log(document.documentElement); // <html> 元素
console.log(document.head); // <head> 元素
console.log(document.body); // <body> 元素
</script>
</body>
</html>DOM 节点类型
javascript
// DOM 节点类型
// Element(元素节点):HTML 元素,如 <div>、<p>
// Text(文本节点):元素中的文本内容
// Attribute(属性节点):元素的属性
// Comment(注释节点):HTML 注释
// Document(文档节点):整个文档
// 获取节点类型
let element = document.getElementById('container');
console.log(element.nodeType); // 1(Element 节点)
console.log(element.nodeName); // 'DIV'
// 节点类型常量
console.log(Node.ELEMENT_NODE); // 1
console.log(Node.TEXT_NODE); // 3
console.log(Node.COMMENT_NODE); // 8
console.log(Node.DOCUMENT_NODE); // 9获取元素
通过 ID 获取
javascript
// getElementById():通过 ID 获取单个元素
let container = document.getElementById('container');
console.log(container);
// 如果 ID 不存在,返回 null
let notExist = document.getElementById('not-exist');
console.log(notExist); // null
// ID 是唯一的,只返回第一个匹配的元素通过标签名获取
javascript
// getElementsByTagName():通过标签名获取元素集合
let paragraphs = document.getElementsByTagName('p');
console.log(paragraphs); // HTMLCollection
// 遍历
for (let i = 0; i < paragraphs.length; i++) {
console.log(paragraphs[i]);
}
// 转换为数组
let arr = Array.from(paragraphs);
// 或
let arr2 = [...paragraphs];
// 在特定元素内查找
let container = document.getElementById('container');
let items = container.getElementsByTagName('li');通过类名获取
javascript
// getElementsByClassName():通过类名获取元素集合
let titles = document.getElementsByClassName('title');
console.log(titles); // HTMLCollection
// 多个类名(空格分隔)
let elements = document.getElementsByClassName('class1 class2');
// 在特定元素内查找
let container = document.getElementById('container');
let items = container.getElementsByClassName('item');通过选择器获取
javascript
// querySelector():获取匹配选择器的第一个元素
let firstParagraph = document.querySelector('p');
let titleElement = document.querySelector('.title');
let container = document.querySelector('#container');
let firstItem = document.querySelector('ul li:first-child');
// querySelectorAll():获取匹配选择器的所有元素
let allParagraphs = document.querySelectorAll('p');
let allItems = document.querySelectorAll('ul li');
let allTitles = document.querySelectorAll('.title');
// NodeList 可以使用 forEach
allParagraphs.forEach(p => {
console.log(p.textContent);
});
// 复杂选择器
let element = document.querySelector('div.container > p.active');
let inputs = document.querySelectorAll('input[type="text"]');特殊元素获取
javascript
// 获取 html 元素
let html = document.documentElement;
// 获取 head 元素
let head = document.head;
// 获取 body 元素
let body = document.body;
// 获取所有表单
let forms = document.forms;
// 获取所有链接
let links = document.links;
// 获取所有图片
let images = document.images;
// 获取当前焦点元素
let activeElement = document.activeElement;
// 获取或设置文档标题
console.log(document.title);
document.title = '新标题';操作元素内容
innerHTML
javascript
let div = document.getElementById('container');
// 获取 HTML 内容
console.log(div.innerHTML);
// 设置 HTML 内容
div.innerHTML = '<p>新的内容</p>';
// 追加内容
div.innerHTML += '<p>追加的内容</p>';
// 清空内容
div.innerHTML = '';
// 注意:innerHTML 有安全风险(XSS攻击)
// 不要直接插入用户输入的内容
let userInput = '<script>alert("XSS")</script>';
// div.innerHTML = userInput; // 危险!textContent
javascript
let div = document.getElementById('container');
// 获取文本内容(包括后代元素的文本)
console.log(div.textContent);
// 设置文本内容(会转义 HTML 标签)
div.textContent = '<p>这不会变成标签</p>';
// 结果:<p>这不会变成标签</p>(纯文本显示)
// textContent 比 innerHTML 更安全
// 适合显示用户输入的内容
div.textContent = userInput; // 安全innerText
javascript
let div = document.getElementById('container');
// innerText 与 textContent 类似
// 但 innerText 会考虑 CSS 样式(如 display: none)
// 考虑样式
console.log(div.innerText); // 不包含隐藏元素的文本
console.log(div.textContent); // 包含所有文本
// 性能:textContent 更快outerHTML
javascript
let div = document.getElementById('container');
// 获取元素及其内容的 HTML
console.log(div.outerHTML);
// <div id="container">内容</div>
// 替换整个元素
div.outerHTML = '<section id="new">新内容</section>';
// 原来的 div 被替换为 section操作元素属性
标准属性
javascript
let img = document.querySelector('img');
// 获取属性
console.log(img.src);
console.log(img.alt);
console.log(img.id);
console.log(img.className); // 注意:是 className 不是 class
// 设置属性
img.src = 'new-image.jpg';
img.alt = '新图片';
img.id = 'new-id';
img.className = 'new-class';
// 表单元素
let input = document.querySelector('input');
console.log(input.value); // 输入值
console.log(input.type); // 输入类型
console.log(input.checked); // 是否选中(checkbox/radio)
console.log(input.disabled); // 是否禁用
input.value = '新值';
input.checked = true;
input.disabled = true;getAttribute 和 setAttribute
javascript
let link = document.querySelector('a');
// 获取属性
console.log(link.getAttribute('href'));
console.log(link.getAttribute('target'));
console.log(link.getAttribute('data-id')); // 自定义属性
// 设置属性
link.setAttribute('href', 'https://example.com');
link.setAttribute('target', '_blank');
link.setAttribute('title', '点击访问');
// 移除属性
link.removeAttribute('target');
// 检查属性是否存在
console.log(link.hasAttribute('href')); // true
// 获取所有属性
let attrs = link.attributes;
for (let attr of attrs) {
console.log(attr.name, attr.value);
}data-* 自定义属性
html
<div id="user" data-id="1001" data-user-name="张三" data-age="25">
用户信息
</div>javascript
let user = document.getElementById('user');
// 使用 dataset 访问 data-* 属性
console.log(user.dataset.id); // '1001'
console.log(user.dataset.userName); // '张三'(驼峰命名)
console.log(user.dataset.age); // '25'
// 设置 data-* 属性
user.dataset.id = '1002';
user.dataset.city = '北京'; // 添加新属性
// 删除 data-* 属性
delete user.dataset.age;
// 使用 getAttribute/setAttribute
console.log(user.getAttribute('data-id')); // '1002'
user.setAttribute('data-role', 'admin');操作元素样式
style 属性
javascript
let div = document.querySelector('div');
// 获取内联样式
console.log(div.style.color);
console.log(div.style.backgroundColor); // 驼峰命名
// 设置内联样式
div.style.color = 'red';
div.style.backgroundColor = '#f0f0f0';
div.style.fontSize = '20px';
div.style.marginTop = '10px';
div.style.display = 'none';
// 设置多个样式
div.style.cssText = 'color: red; font-size: 20px; margin: 10px;';
// 清除样式
div.style.color = '';
// 或
div.removeAttribute('style');className 和 classList
javascript
let div = document.querySelector('div');
// className:获取/设置类名
console.log(div.className); // 'box active'
div.className = 'new-class';
div.className += ' another-class'; // 追加类名
// classList:更方便的操作方式
console.log(div.classList); // DOMTokenList
// 添加类
div.classList.add('active');
div.classList.add('class1', 'class2'); // 添加多个
// 移除类
div.classList.remove('active');
div.classList.remove('class1', 'class2');
// 切换类(有则移除,无则添加)
div.classList.toggle('active');
div.classList.toggle('active', true); // 强制添加
div.classList.toggle('active', false); // 强制移除
// 检查是否包含类
console.log(div.classList.contains('active')); // true/false
// 替换类
div.classList.replace('old-class', 'new-class');
// 遍历类名
div.classList.forEach(className => {
console.log(className);
});获取计算样式
javascript
let div = document.querySelector('div');
// getComputedStyle():获取最终应用的样式
let style = window.getComputedStyle(div);
console.log(style.color);
console.log(style.backgroundColor);
console.log(style.width);
console.log(style.fontSize);
// 获取特定伪元素的样式
let beforeStyle = window.getComputedStyle(div, '::before');
console.log(beforeStyle.content);
// 注意:getComputedStyle 返回的是只读对象
// style.color = 'red'; // 无效创建和插入元素
创建元素
javascript
// createElement():创建元素节点
let div = document.createElement('div');
div.id = 'new-div';
div.className = 'box';
div.textContent = '新创建的 div';
// createTextNode():创建文本节点
let text = document.createTextNode('文本内容');
// createDocumentFragment():创建文档片段(用于批量操作)
let fragment = document.createDocumentFragment();
// createComment():创建注释节点
let comment = document.createComment('这是注释');插入元素
javascript
let container = document.getElementById('container');
let newDiv = document.createElement('div');
newDiv.textContent = '新元素';
// appendChild():添加到末尾
container.appendChild(newDiv);
// insertBefore():插入到指定元素之前
let reference = document.getElementById('reference');
container.insertBefore(newDiv, reference);
// 插入到第一个子元素之前
container.insertBefore(newDiv, container.firstChild);
// append():添加多个节点到末尾 - ES2015
container.append(newDiv, '文本', anotherElement);
// prepend():添加到开头
container.prepend(newDiv);
// before():插入到当前元素之前
reference.before(newDiv);
// after():插入到当前元素之后
reference.after(newDiv);
// replaceWith():替换当前元素
reference.replaceWith(newDiv);克隆元素
javascript
let original = document.getElementById('original');
// cloneNode():克隆元素
// 参数 false(默认):只克隆元素本身
// 参数 true:深度克隆,包括后代元素
let shallow = original.cloneNode(); // 浅克隆
let deep = original.cloneNode(true); // 深克隆
// 注意:克隆的元素不会复制事件监听器
// 克隆的元素 id 会重复,需要修改
deep.id = 'cloned-element';删除元素
javascript
let element = document.getElementById('to-remove');
// remove():删除元素本身
element.remove();
// removeChild():删除子元素
let parent = document.getElementById('parent');
let child = document.getElementById('child');
parent.removeChild(child);
// replaceChild():替换子元素
let newChild = document.createElement('div');
parent.replaceChild(newChild, child);
// 清空元素内容
parent.innerHTML = '';
// 或
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}遍历 DOM
父子关系
javascript
let element = document.getElementById('element');
// 父节点
console.log(element.parentNode); // 父节点
console.log(element.parentElement); // 父元素节点
// 子节点
console.log(element.childNodes); // 所有子节点(包括文本节点)
console.log(element.children); // 所有子元素节点
// 第一个/最后一个子节点
console.log(element.firstChild); // 第一个子节点(可能是文本节点)
console.log(element.firstElementChild); // 第一个子元素
console.log(element.lastChild); // 最后一个子节点
console.log(element.lastElementChild); // 最后一个子元素
// 子节点数量
console.log(element.childNodes.length);
console.log(element.children.length);
console.log(element.childElementCount); // 子元素数量兄弟关系
javascript
let element = document.getElementById('element');
// 下一个兄弟节点
console.log(element.nextSibling); // 下一个兄弟节点(可能是文本节点)
console.log(element.nextElementSibling); // 下一个兄弟元素
// 上一个兄弟节点
console.log(element.previousSibling); // 上一个兄弟节点
console.log(element.previousElementSibling); // 上一个兄弟元素
// 遍历所有兄弟元素
let sibling = element.nextElementSibling;
while (sibling) {
console.log(sibling);
sibling = sibling.nextElementSibling;
}遍历所有后代元素
javascript
let container = document.getElementById('container');
// getElementsByTagName()
let allDivs = container.getElementsByTagName('div');
// querySelectorAll()
let allElements = container.querySelectorAll('*');
// 递归遍历
function traverse(element, callback) {
callback(element);
for (let child of element.children) {
traverse(child, callback);
}
}
traverse(container, el => {
console.log(el.tagName);
});
// TreeWalker API
let walker = document.createTreeWalker(
container,
NodeFilter.SHOW_ELEMENT,
null,
false
);
let node;
while (node = walker.nextNode()) {
console.log(node.tagName);
}元素尺寸和位置
offset 系列
javascript
let element = document.getElementById('element');
// offsetWidth / offsetHeight:元素可见宽高(包括 padding、border)
console.log(element.offsetWidth);
console.log(element.offsetHeight);
// offsetLeft / offsetTop:相对于 offsetParent 的偏移
console.log(element.offsetLeft);
console.log(element.offsetTop);
// offsetParent:最近的定位祖先元素
console.log(element.offsetParent);client 系列
javascript
let element = document.getElementById('element');
// clientWidth / clientHeight:内容区域宽高(包括 padding,不包括 border、滚动条)
console.log(element.clientWidth);
console.log(element.clientHeight);
// clientLeft / clientTop:左边框/上边框宽度
console.log(element.clientLeft);
console.log(element.clientTop);scroll 系列
javascript
let element = document.getElementById('element');
// scrollWidth / scrollHeight:内容实际宽高(包括溢出部分)
console.log(element.scrollWidth);
console.log(element.scrollHeight);
// scrollLeft / scrollTop:滚动位置
console.log(element.scrollLeft);
console.log(element.scrollTop);
// 设置滚动位置
element.scrollTop = 100;
element.scrollLeft = 50;
// 滚动到指定位置
element.scrollTo({
top: 100,
left: 0,
behavior: 'smooth' // 平滑滚动
});
// 滚动到指定元素
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});getBoundingClientRect
javascript
let element = document.getElementById('element');
// getBoundingClientRect():获取元素相对于视口的位置和尺寸
let rect = element.getBoundingClientRect();
console.log(rect.top); // 上边距视口顶部的距离
console.log(rect.bottom); // 下边距视口顶部的距离
console.log(rect.left); // 左边距视口左侧的距离
console.log(rect.right); // 右边距视口左侧的距离
console.log(rect.width); // 元素宽度
console.log(rect.height); // 元素高度
console.log(rect.x); // 等同于 left
console.log(rect.y); // 等同于 top
// 检测元素是否在视口中
function isInViewport(element) {
let rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
}
// 获取元素在文档中的绝对位置
function getAbsolutePosition(element) {
let rect = element.getBoundingClientRect();
return {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX
};
}表单操作
获取表单元素
javascript
// 通过 name 属性获取
let form = document.forms['myForm'];
// 或
let form = document.forms.myForm;
// 通过索引获取
let firstForm = document.forms[0];
// 获取表单中的元素
let username = form.elements.username;
let password = form.elements['password'];
let submitBtn = form.elements.submitBtn;
// 通过索引获取
let firstInput = form.elements[0];表单属性和方法
javascript
let form = document.getElementById('myForm');
// 表单属性
console.log(form.action); // 提交地址
console.log(form.method); // 提交方式
console.log(form.enctype); // 编码类型
console.log(form.name); // 表单名称
// 表单方法
form.submit(); // 提交表单
form.reset(); // 重置表单
// 表单元素属性
let input = document.getElementById('username');
console.log(input.value); // 输入值
console.log(input.defaultValue); // 默认值
console.log(input.type); // 输入类型
console.log(input.name); // 名称
console.log(input.disabled); // 是否禁用
console.log(input.readOnly); // 是否只读
console.log(input.required); // 是否必填
// 设置值
input.value = '新值';
input.disabled = true;
input.focus(); // 获取焦点
input.blur(); // 失去焦点
input.select(); // 选中内容表单验证
javascript
let form = document.getElementById('myForm');
// HTML5 验证属性
// required:必填
// pattern:正则验证
// min/max:最小/最大值
// minlength/maxlength:最小/最大长度
// type="email":邮箱格式
// type="url":URL 格式
// 检查表单有效性
form.addEventListener('submit', function(e) {
if (!form.checkValidity()) {
e.preventDefault(); // 阻止提交
// 显示验证信息
let invalidInputs = form.querySelectorAll(':invalid');
invalidInputs.forEach(input => {
console.log(input.name + ': ' + input.validationMessage);
});
}
});
// 手动验证
let input = document.getElementById('email');
console.log(input.validity.valid); // 是否有效
console.log(input.validity.valueMissing); // 是否缺失必填值
console.log(input.validity.typeMismatch); // 类型是否匹配
console.log(input.validity.patternMismatch); // 是否匹配模式
// 设置自定义验证信息
input.setCustomValidity('请输入有效的邮箱地址');
if (input.validity.valid) {
input.setCustomValidity(''); // 清除验证信息
}
// 报告验证结果
input.reportValidity(); // 显示验证提示处理表单提交
javascript
let form = document.getElementById('myForm');
// 监听提交事件
form.addEventListener('submit', function(e) {
e.preventDefault(); // 阻止默认提交
// 获取表单数据
let formData = new FormData(form);
// 遍历数据
for (let [key, value] of formData) {
console.log(key, value);
}
// 获取特定字段
let username = formData.get('username');
let password = formData.get('password');
// 获取多选值
let hobbies = formData.getAll('hobby');
// 转换为对象
let data = Object.fromEntries(formData);
console.log(data);
// 转换为 JSON
let json = JSON.stringify(data);
// 发送 AJAX 请求
fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: json
})
.then(response => response.json())
.then(data => {
console.log('成功:', data);
});
});小结
本章学习了 DOM 操作的核心知识:
- DOM 简介:DOM 树结构、节点类型
- 获取元素:getElementById、getElementsByTagName、getElementsByClassName、querySelector
- 操作内容:innerHTML、textContent、innerText、outerHTML
- 操作属性:标准属性、getAttribute/setAttribute、data-* 属性
- 操作样式:style、className、classList、getComputedStyle
- 创建插入:createElement、appendChild、insertBefore、append/prepend
- 遍历 DOM:父子关系、兄弟关系
- 尺寸位置:offset、client、scroll、getBoundingClientRect
- 表单操作:获取表单、表单验证、处理提交
下一章我们将学习 事件处理,了解如何响应用户交互。
