Day 12: 表单处理
DOM 操作 – 第3天
🎯 学习目标
- 理解 HTML 表单的基本结构
- 掌握表单元素的访问和操作
- 学会获取和验证用户输入
- 实现表单数据的提交和处理
💡 核心概念
什么是表单?
表单(Form) 是网页上收集用户输入的主要方式,常见场景包括:
- 用户登录/注册
- 搜索框
- 评论反馈
- 在线问卷
- 购物车结算
表单的基本结构
<form id="myForm" action="/submit" method="POST">
<!-- 表单元素 -->
<input type="text" name="username" placeholder="用户名">
<input type="password" name="password" placeholder="密码">
<button type="submit">提交</button>
</form>
重要属性:
action: 表单提交的 URLmethod: 提交方式(GET 或 POST)id: 唯一标识符(用于 JavaScript 操作)
常见表单元素
| 元素 | type 值 | 用途 |
|---|---|---|
<input> |
text | 单行文本 |
<input> |
password | 密码(隐藏输入) |
<input> |
邮箱地址 | |
<input> |
number | 数字 |
<input> |
checkbox | 复选框 |
<input> |
radio | 单选按钮 |
<select> |
– | 下拉列表 |
<textarea> |
– | 多行文本 |
<button> |
submit | 提交按钮 |
📝 DOM 操作:访问表单元素
方法 1:通过 ID 获取
// 获取单个元素
const usernameInput = document.getElementById('username');
// 获取值
const value = usernameInput.value;
console.log(value);
// 设置值
usernameInput.value = '新值';
方法 2:通过表单的 elements 集合
const form = document.getElementById('myForm');
// 通过 name 属性获取
const username = form.elements.username;
// 或
const username = form.elements['username'];
// 通过索引获取
const firstInput = form.elements[0];
方法 3:通过 querySelector
// 获取单个元素
const input = document.querySelector('#username');
// 获取特定类型的输入框
const textInputs = document.querySelectorAll('input[type="text"]');
// 获取所有选中的复选框
const checkedBoxes = document.querySelectorAll('input[type="checkbox"]:checked');
🎮 实战示例
示例 1:用户注册表单验证
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ddd;
border-radius: 4px;
}
input:focus {
outline: none;
border-color: #4CAF50;
}
.error {
color: red;
font-size: 12px;
margin-top: 5px;
display: none;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}
button:hover {
background-color: #45a049;
}
.success {
background-color: #d4edda;
color: #155724;
padding: 10px;
border-radius: 4px;
margin-top: 20px;
display: none;
}
</style>
</head>
<body>
<h2>用户注册</h2>
<form id="registerForm">
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" placeholder="请输入用户名(3-15个字符)">
<div class="error" id="usernameError">用户名长度必须在 3-15 个字符之间</div>
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" placeholder="请输入邮箱">
<div class="error" id="emailError">请输入有效的邮箱地址</div>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" name="password" placeholder="请输入密码(至少6个字符)">
<div class="error" id="passwordError">密码长度必须至少 6 个字符</div>
</div>
<div class="form-group">
<label for="confirmPassword">确认密码:</label>
<input type="password" id="confirmPassword" name="confirmPassword" placeholder="请再次输入密码">
<div class="error" id="confirmError">两次输入的密码不一致</div>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="agree" name="agree">
我同意用户协议
</label>
<div class="error" id="agreeError">请同意用户协议</div>
</div>
<button type="submit">注册</button>
</form>
<div class="success" id="successMessage">
注册成功!欢迎加入我们。
</div>
<script>
// 获取表单和元素
const form = document.getElementById('registerForm');
const usernameInput = document.getElementById('username');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirmPassword');
const agreeCheckbox = document.getElementById('agree');
// 显示错误
function showError(input, errorId) {
const errorElement = document.getElementById(errorId);
errorElement.style.display = 'block';
input.style.borderColor = 'red';
}
// 隐藏错误
function hideError(input, errorId) {
const errorElement = document.getElementById(errorId);
errorElement.style.display = 'none';
input.style.borderColor = '#ddd';
}
// 验证用户名
function validateUsername() {
const username = usernameInput.value.trim();
if (username.length < 3 || username.length > 15) {
showError(usernameInput, 'usernameError');
return false;
}
hideError(usernameInput, 'usernameError');
return true;
}
// 验证邮箱
function validateEmail() {
const email = emailInput.value.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
showError(emailInput, 'emailError');
return false;
}
hideError(emailInput, 'emailError');
return true;
}
// 验证密码
function validatePassword() {
const password = passwordInput.value;
if (password.length < 6) {
showError(passwordInput, 'passwordError');
return false;
}
hideError(passwordInput, 'passwordError');
return true;
}
// 验证确认密码
function validateConfirmPassword() {
const password = passwordInput.value;
const confirmPassword = confirmPasswordInput.value;
if (password !== confirmPassword) {
showError(confirmPasswordInput, 'confirmError');
return false;
}
hideError(confirmPasswordInput, 'confirmError');
return true;
}
// 验证协议
function validateAgree() {
if (!agreeCheckbox.checked) {
document.getElementById('agreeError').style.display = 'block';
return false;
}
document.getElementById('agreeError').style.display = 'none';
return true;
}
// 实时验证(用户输入时)
usernameInput.addEventListener('input', validateUsername);
emailInput.addEventListener('input', validateEmail);
passwordInput.addEventListener('input', validatePassword);
confirmPasswordInput.addEventListener('input', validateConfirmPassword);
agreeCheckbox.addEventListener('change', validateAgree);
// 表单提交处理
form.addEventListener('submit', function(event) {
// 阻止默认提交行为
event.preventDefault();
// 验证所有字段
const isUsernameValid = validateUsername();
const isEmailValid = validateEmail();
const isPasswordValid = validatePassword();
const isConfirmValid = validateConfirmPassword();
const isAgreeValid = validateAgree();
// 如果所有验证都通过
if (isUsernameValid && isEmailValid && isPasswordValid && isConfirmValid && isAgreeValid) {
// 收集表单数据
const formData = {
username: usernameInput.value.trim(),
email: emailInput.value.trim(),
password: passwordInput.value
};
// 这里可以将数据发送到服务器
console.log('注册数据:', formData);
// 显示成功消息
document.getElementById('successMessage').style.display = 'block';
// 清空表单
form.reset();
// 3秒后隐藏成功消息
setTimeout(() => {
document.getElementById('successMessage').style.display = 'none';
}, 3000);
}
});
</script>
</body>
</html>
示例 2:搜索框实时过滤
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时搜索</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.search-box {
margin-bottom: 20px;
}
input[type="text"] {
width: 100%;
padding: 10px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
input[type="text"]:focus {
outline: none;
border-color: #4CAF50;
}
.item-list {
list-style: none;
padding: 0;
}
.item {
padding: 15px;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background-color 0.2s;
}
.item:hover {
background-color: #f5f5f5;
}
.item.hidden {
display: none;
}
.item-name {
font-weight: bold;
margin-bottom: 5px;
}
.item-desc {
color: #666;
font-size: 14px;
}
.no-results {
text-align: center;
color: #999;
padding: 20px;
display: none;
}
</style>
</head>
<body>
<h2>实时搜索过滤</h2>
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索...">
</div>
<ul class="item-list" id="itemList">
<li class="item">
<div class="item-name">JavaScript</div>
<div class="item-desc">一种动态编程语言,广泛用于网页开发</div>
</li>
<li class="item">
<div class="item-name">Python</div>
<div class="item-desc">简洁易学的编程语言,适合初学者</div>
</li>
<li class="item">
<div class="item-name">Java</div>
<div class="item-desc">跨平台的面向对象编程语言</div>
</li>
<li class="item">
<div class="item-name">C++</div>
<div class="item-desc">高性能的编程语言,用于系统开发</div>
</li>
<li class="item">
<div class="item-name">HTML</div>
<div class="item-desc">网页的标记语言,定义网页结构</div>
</li>
<li class="item">
<div class="item-name">CSS</div>
<div class="item-desc">样式表语言,控制网页外观</div>
</li>
<li class="item">
<div class="item-name">React</div>
<div class="item-desc">Facebook 开发的 JavaScript 库</div>
</li>
<li class="item">
<div class="item-name">Vue.js</div>
<div class="item-desc">渐进式 JavaScript 框架</div>
</li>
</ul>
<div class="no-results" id="noResults">
没有找到匹配的结果
</div>
<script>
const searchInput = document.getElementById('searchInput');
const items = document.querySelectorAll('.item');
const noResults = document.getElementById('noResults');
// 实时搜索
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase().trim();
let visibleCount = 0;
items.forEach(item => {
const name = item.querySelector('.item-name').textContent.toLowerCase();
const desc = item.querySelector('.item-desc').textContent.toLowerCase();
// 检查名称或描述是否包含搜索词
if (name.includes(searchTerm) || desc.includes(searchTerm)) {
item.classList.remove('hidden');
visibleCount++;
} else {
item.classList.add('hidden');
}
});
// 显示/隐藏"无结果"消息
if (visibleCount === 0) {
noResults.style.display = 'block';
} else {
noResults.style.display = 'none';
}
});
</script>
</body>
</html>
示例 3:表单数据收集和序列化
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单数据收集</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, select, textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ddd;
border-radius: 4px;
}
textarea {
min-height: 80px;
resize: vertical;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
.output {
margin-top: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
white-space: pre-wrap;
font-family: monospace;
}
</style>
</head>
<body>
<h2>用户信息收集</h2>
<form id="infoForm">
<div class="form-group">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="number" id="age" name="age" min="1" max="120">
</div>
<div class="form-group">
<label for="gender">性别:</label>
<select id="gender" name="gender">
<option value="">请选择</option>
<option value="male">男</option>
<option value="female">女</option>
<option value="other">其他</option>
</select>
</div>
<div class="form-group">
<label>兴趣:</label>
<label>
<input type="checkbox" name="hobbies" value="reading"> 阅读
</label>
<label>
<input type="checkbox" name="hobbies" value="sports"> 运动
</label>
<label>
<input type="checkbox" name="hobbies" value="music"> 音乐
</label>
<label>
<input type="checkbox" name="hobbies" value="travel"> 旅行
</label>
</div>
<div class="form-group">
<label for="bio">个人简介:</label>
<textarea id="bio" name="bio"></textarea>
</div>
<button type="submit">提交</button>
<button type="button" id="showJsonBtn">显示 JSON</button>
<button type="button" id="resetBtn">重置</button>
</form>
<div class="output" id="output"></div>
<script>
const form = document.getElementById('infoForm');
const output = document.getElementById('output');
const showJsonBtn = document.getElementById('showJsonBtn');
const resetBtn = document.getElementById('resetBtn');
// 方法 1:手动收集表单数据
function collectFormData() {
const data = {
name: form.elements.name.value,
age: parseInt(form.elements.age.value) || 0,
gender: form.elements.gender.value,
hobbies: [],
bio: form.elements.bio.value
};
// 收集选中的复选框
const checkboxes = form.querySelectorAll('input[name="hobbies"]:checked');
checkboxes.forEach(cb => {
data.hobbies.push(cb.value);
});
return data;
}
// 方法 2:使用 FormData
function collectFormDataObject() {
const formData = new FormData(form);
const data = {};
// 遍历 FormData
for (let [key, value] of formData.entries()) {
// 处理多选值(复选框)
if (data[key]) {
if (Array.isArray(data[key])) {
data[key].push(value);
} else {
data[key] = [data[key], value];
}
} else {
data[key] = value;
}
}
return data;
}
// 显示输出
function displayOutput(data, title = '表单数据') {
output.textContent = `${title}:\n${JSON.stringify(data, null, 2)}`;
}
// 表单提交
form.addEventListener('submit', function(event) {
event.preventDefault();
const data = collectFormData();
displayOutput(data, '提交的数据');
console.log('表单数据:', data);
});
// 显示 JSON 按钮
showJsonBtn.addEventListener('click', function() {
const data = collectFormDataObject();
displayOutput(data, 'FormData 对象');
});
// 重置按钮
resetBtn.addEventListener('click', function() {
form.reset();
output.textContent = '';
});
</script>
</body>
</html>
📊 表单验证技巧
1. 实时验证 vs 提交时验证
// 实时验证(用户输入时)
input.addEventListener('input', validate);
// 失去焦点验证
input.addEventListener('blur', validate);
// 提交时验证
form.addEventListener('submit', function(event) {
if (!validate()) {
event.preventDefault(); // 阻止提交
}
});
2. 正则表达式验证
// 邮箱验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// 手机号验证(中国)
const phoneRegex = /^1[3-9]\d{9}$/;
// 用户名验证(字母开头,允许字母数字下划线,4-16位)
const usernameRegex = /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/;
// 密码验证(至少8位,包含字母和数字)
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
// 使用示例
function validateEmail(email) {
return emailRegex.test(email);
}
3. 自定义验证样式
// 添加验证样式
function setValid(input) {
input.style.borderColor = '#4CAF50';
input.classList.add('valid');
input.classList.remove('invalid');
}
function setInvalid(input) {
input.style.borderColor = '#f44336';
input.classList.add('invalid');
input.classList.remove('valid');
}
// CSS 样式
/*
input.valid {
border-color: #4CAF50;
}
input.invalid {
border-color: #f44336;
}
input.valid::after {
content: '✓';
color: #4CAF50;
}
input.invalid::after {
content: '✗';
color: #f44336;
}
*/
⚠️ 重要注意事项
1. 永远不要只依赖客户端验证
// ❌ 错误:只验证前端
form.addEventListener('submit', function(event) {
if (isValid) {
// 直接提交,不安全!
}
});
// ✅ 正确:前端验证 + 后端验证
form.addEventListener('submit', function(event) {
if (isValid) {
// 发送到服务器进行验证
fetch('/api/register', {
method: 'POST',
body: JSON.stringify(formData)
}).then(response => {
// 处理服务器验证结果
});
}
});
2. 防止 XSS 攻击
// ❌ 危险:直接显示用户输入
div.innerHTML = userInput;
// ✅ 安全:转义用户输入
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
div.textContent = userInput; // 或使用 textContent
3. 处理表单重置
// 重置表单时,也要重置自定义样式
resetBtn.addEventListener('click', function() {
form.reset();
// 重置验证状态
document.querySelectorAll('.error').forEach(el => {
el.style.display = 'none';
});
document.querySelectorAll('input').forEach(input => {
input.style.borderColor = '#ddd';
});
});
✍️ 练习任务
练习 1:登录表单(30分钟)
创建一个登录表单,包含用户名和密码字段,实现基本的验证。
要求:
- 用户名至少 3 个字符
- 密码至少 6 个字符
- 显示错误提示
- 提交时验证所有字段
练习 2:问卷调查(1小时)
创建一个问卷调查表单,包含多种输入类型。
要求:
- 单选题(性别)
- 多选题(兴趣)
- 文本框(姓名)
- 下拉选择(城市)
- 文本域(建议)
- 提交时验证必填项
- 显示收集的数据
练习 3:购物车表单(2小时)
创建一个购物车结算表单。
要求:
- 商品列表(可勾选)
- 数量选择
- 配送信息表单
- 支付方式选择
- 实时计算总价
- 提交前验证所有信息
🎓 今日挑战
挑战任务:创建一个完整的用户注册系统
功能要求:
- 实时验证用户名(检查用户名是否已存在,使用模拟数据)
- 密码强度指示器
- 邮箱验证(发送验证码倒计时)
- 手机号验证(模拟发送验证码)
- 同意协议必须勾选
- 提交后显示加载动画
- 模拟异步提交到服务器
评估标准:
- ✅ 所有验证正确实现
- ✅ 用户体验良好(实时反馈)
- ✅ 代码结构清晰
- ✅ 界面美观
💡 常见问题
Q1: submit 事件和 click 事件有什么区别?
A:
submit:在<form>元素上,按回车键或点击提交按钮时触发click:在按钮上,只有点击时触发
推荐:使用 submit 事件,因为它能捕获所有提交方式。
Q2: 如何禁用表单自动完成?
A:
<input type="text" autocomplete="off">
<!-- 或 -->
<form autocomplete="off">
Q3: FormData 对象有哪些优势?
A:
- 自动处理多选值
- 支持文件上传
- 可以直接用于 fetch/XMLHttpRequest
- 跨浏览器兼容性好
const formData = new FormData(form);
fetch('/api/submit', {
method: 'POST',
body: formData // 自动设置正确的 Content-Type
});
📚 拓展阅读
- MDN 文档: HTML Forms
- 相关章节:
- Day 10: DOM 操作基础
- Day 11: 事件处理
- Day 13: CSS 基础
🔗 相关资源
学习时间: 2026-03-12
难度: ⭐⭐⭐☆☆
预计用时: 3-4 小时
关键词: 表单, 表单验证, DOM 操作, FormData
相关标签: #07-DOM操作 #JavaScript #前端开发
下一步: 学习 Day 13: CSS 基础