JavaScript 表单
表单是 Web 应用程序中用户与应用程序交互的核心组件。JavaScript 为表单处理提供了丰富的 API,可以实现表单验证、数据处理、动态交互等功能。掌握表单处理技术对于创建用户友好的 Web 应用程序至关重要。在本章节中,我们将深入学习 JavaScript 中的表单处理技术。
什么是表单
表单是 HTML 中用于收集用户输入的元素集合。通过 JavaScript,我们可以动态地操作表单元素、验证用户输入、处理表单提交等。
html
<!-- 基本表单示例 -->
<form id="userForm">
<div>
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="age">年龄:</label>
<input type="number" id="age" name="age" min="0" max="150">
</div>
<div>
<label for="message">留言:</label>
<textarea id="message" name="message" rows="4"></textarea>
</div>
<button type="submit">提交</button>
</form>表单元素访问
通过 ID 访问
javascript
// 通过 getElementById 访问表单元素
const nameInput = document.getElementById("name");
const emailInput = document.getElementById("email");
// 获取和设置值
console.log(nameInput.value); // 获取值
nameInput.value = "张三"; // 设置值
// 获取和设置属性
console.log(emailInput.type); // "email"
console.log(emailInput.required); // true通过表单对象访问
javascript
// 通过表单访问元素
const form = document.getElementById("userForm");
// 通过 name 属性访问
const nameField = form.elements.name;
const emailField = form.elements.email;
// 通过索引访问
const firstField = form.elements[0];
// 通过 ID 访问
const ageField = form.elements["age"];表单集合操作
javascript
const form = document.getElementById("userForm");
// 遍历所有表单元素
for (let i = 0; i < form.elements.length; i++) {
const element = form.elements[i];
console.log(`元素 ${i}: ${element.name} = ${element.value}`);
}
// 转换为数组进行操作
const formElements = Array.from(form.elements);
formElements.forEach(element => {
if (element.type === "text" || element.type === "email") {
element.style.border = "1px solid #ccc";
}
});表单事件处理
表单提交事件
javascript
const form = document.getElementById("userForm");
// 监听表单提交
form.addEventListener("submit", function(event) {
// 阻止默认提交行为
event.preventDefault();
// 处理表单数据
console.log("表单提交");
processFormData();
});
function processFormData() {
// 获取表单数据
const formData = new FormData(form);
// 遍历表单数据
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// 转换为普通对象
const data = Object.fromEntries(formData);
console.log("表单数据:", data);
}字段事件处理
javascript
// 输入事件
const nameInput = document.getElementById("name");
nameInput.addEventListener("input", function(event) {
console.log("输入内容变化:", event.target.value);
});
// 焦点事件
nameInput.addEventListener("focus", function(event) {
console.log("获得焦点");
event.target.style.backgroundColor = "#f0f0f0";
});
nameInput.addEventListener("blur", function(event) {
console.log("失去焦点");
event.target.style.backgroundColor = "";
});
// 变化事件
const ageInput = document.getElementById("age");
ageInput.addEventListener("change", function(event) {
console.log("值发生变化:", event.target.value);
});实时验证
javascript
// 实时验证示例
class FormValidator {
constructor(form) {
this.form = form;
this.init();
}
init() {
// 为所有输入字段添加实时验证
this.form.querySelectorAll("input, textarea, select").forEach(field => {
field.addEventListener("blur", () => this.validateField(field));
field.addEventListener("input", () => this.clearFieldError(field));
});
// 表单提交验证
this.form.addEventListener("submit", (event) => this.handleSubmit(event));
}
validateField(field) {
const fieldName = field.name;
const value = field.value.trim();
let errorMessage = "";
// 根据字段类型进行验证
switch (fieldName) {
case "name":
if (!value) {
errorMessage = "姓名是必需的";
} else if (value.length < 2) {
errorMessage = "姓名至少需要2个字符";
}
break;
case "email":
if (!value) {
errorMessage = "邮箱是必需的";
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
errorMessage = "请输入有效的邮箱地址";
}
break;
case "age":
if (value && (isNaN(value) || value < 0 || value > 150)) {
errorMessage = "请输入有效的年龄(0-150)";
}
break;
}
this.setFieldError(field, errorMessage);
return !errorMessage;
}
setFieldError(field, errorMessage) {
// 清除之前的错误样式
field.classList.remove("error");
// 移除之前的错误消息
const existingError = field.parentNode.querySelector(".error-message");
if (existingError) {
existingError.remove();
}
if (errorMessage) {
// 添加错误样式
field.classList.add("error");
// 显示错误消息
const errorElement = document.createElement("div");
errorElement.className = "error-message";
errorElement.textContent = errorMessage;
field.parentNode.appendChild(errorElement);
}
}
clearFieldError(field) {
field.classList.remove("error");
const errorElement = field.parentNode.querySelector(".error-message");
if (errorElement) {
errorElement.remove();
}
}
handleSubmit(event) {
event.preventDefault();
// 验证所有字段
let isValid = true;
this.form.querySelectorAll("input, textarea, select").forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
if (isValid) {
this.submitForm();
} else {
// 滚动到第一个错误字段
const firstError = this.form.querySelector(".error");
if (firstError) {
firstError.scrollIntoView({ behavior: "smooth", block: "center" });
firstError.focus();
}
}
}
async submitForm() {
const submitButton = this.form.querySelector("button[type='submit']");
const originalText = submitButton.textContent;
try {
// 显示加载状态
submitButton.disabled = true;
submitButton.textContent = "提交中...";
// 获取表单数据
const formData = new FormData(this.form);
const data = Object.fromEntries(formData);
// 发送数据到服务器
const response = await fetch("/api/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});
if (response.ok) {
this.showMessage("提交成功!", "success");
this.form.reset(); // 重置表单
} else {
throw new Error("提交失败");
}
} catch (error) {
this.showMessage("提交失败,请稍后重试", "error");
console.error("提交错误:", error);
} finally {
// 恢复按钮状态
submitButton.disabled = false;
submitButton.textContent = originalText;
}
}
showMessage(message, type) {
// 移除之前的消息
const existingMessage = this.form.querySelector(".form-message");
if (existingMessage) {
existingMessage.remove();
}
// 创建新消息
const messageElement = document.createElement("div");
messageElement.className = `form-message message-${type}`;
messageElement.textContent = message;
// 添加到表单顶部
this.form.insertBefore(messageElement, this.form.firstChild);
// 3秒后自动移除
setTimeout(() => {
if (messageElement.parentNode) {
messageElement.remove();
}
}, 3000);
}
}
// 使用示例
// const validator = new FormValidator(document.getElementById("userForm"));表单数据处理
FormData 对象
javascript
const form = document.getElementById("userForm");
// 创建 FormData 对象
const formData = new FormData(form);
// 添加额外数据
formData.append("timestamp", new Date().toISOString());
formData.append("userId", "123");
// 获取数据
console.log(formData.get("name")); // 获取单个值
console.log(formData.getAll("hobbies")); // 获取所有同名字段的值
// 遍历数据
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// 转换为对象
const dataObject = Object.fromEntries(formData);
console.log(dataObject);
// 转换为 JSON
const jsonData = JSON.stringify(dataObject);
console.log(jsonData);手动构建表单数据
javascript
// 手动构建表单数据对象
function getFormData(form) {
const data = {};
// 处理不同类型的表单元素
for (let element of form.elements) {
if (!element.name || element.disabled) continue;
switch (element.type) {
case "checkbox":
if (element.checked) {
if (data[element.name]) {
// 多个同名复选框
if (Array.isArray(data[element.name])) {
data[element.name].push(element.value);
} else {
data[element.name] = [data[element.name], element.value];
}
} else {
data[element.name] = element.value;
}
}
break;
case "radio":
if (element.checked) {
data[element.name] = element.value;
}
break;
case "select-multiple":
const selectedOptions = Array.from(element.selectedOptions)
.map(option => option.value);
data[element.name] = selectedOptions;
break;
case "file":
// 文件字段需要特殊处理
data[element.name] = element.files;
break;
default:
data[element.name] = element.value;
}
}
return data;
}
// 使用示例
const formData = getFormData(document.getElementById("userForm"));
console.log("表单数据:", formData);表单验证
HTML5 内置验证
html
<form id="validationForm">
<!-- 必填字段 -->
<input type="text" name="requiredField" required>
<!-- 邮箱验证 -->
<input type="email" name="email" required>
<!-- 数字范围 -->
<input type="number" name="age" min="0" max="150" required>
<!-- 长度限制 -->
<input type="text" name="username" minlength="3" maxlength="20" required>
<!-- 正则表达式 -->
<input type="text" name="phone" pattern="\d{3}-\d{3}-\d{4}"
title="请输入格式为 XXX-XXX-XXXX 的电话号码">
<!-- 自定义验证消息 -->
<input type="text" name="custom" required
oninvalid="this.setCustomValidity('这是自定义错误消息')"
oninput="this.setCustomValidity('')">
</form>JavaScript 验证
javascript
// 高级表单验证类
class AdvancedFormValidator {
constructor(form, rules = {}) {
this.form = form;
this.rules = rules;
this.errors = new Map();
this.init();
}
init() {
// 为表单字段添加验证规则
Object.keys(this.rules).forEach(fieldName => {
const field = this.form.elements[fieldName];
if (field) {
field.addEventListener("blur", () => this.validateField(fieldName));
field.addEventListener("input", () => this.clearError(fieldName));
}
});
// 表单提交验证
this.form.addEventListener("submit", (event) => this.handleSubmit(event));
}
validateField(fieldName) {
const field = this.form.elements[fieldName];
const value = field.value.trim();
const rules = this.rules[fieldName];
// 清除之前的错误
this.clearError(fieldName);
// 执行验证规则
for (let rule of rules) {
const isValid = this.executeRule(rule, value, field);
if (!isValid) {
this.setError(fieldName, rule.message);
return false;
}
}
return true;
}
executeRule(rule, value, field) {
switch (rule.type) {
case "required":
return value !== "";
case "minLength":
return value.length >= rule.value;
case "maxLength":
return value.length <= rule.value;
case "pattern":
return new RegExp(rule.value).test(value);
case "email":
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
case "number":
return !isNaN(value) && !isNaN(parseFloat(value));
case "min":
return parseFloat(value) >= rule.value;
case "max":
return parseFloat(value) <= rule.value;
case "custom":
return rule.validator(value, field, this.form);
default:
return true;
}
}
setError(fieldName, message) {
this.errors.set(fieldName, message);
const field = this.form.elements[fieldName];
field.classList.add("error");
// 显示错误消息
const errorElement = document.createElement("div");
errorElement.className = "error-message";
errorElement.textContent = message;
errorElement.id = `error-${fieldName}`;
// 移除之前的错误消息
const existingError = document.getElementById(`error-${fieldName}`);
if (existingError) {
existingError.remove();
}
field.parentNode.appendChild(errorElement);
}
clearError(fieldName) {
this.errors.delete(fieldName);
const field = this.form.elements[fieldName];
field.classList.remove("error");
const errorElement = document.getElementById(`error-${fieldName}`);
if (errorElement) {
errorElement.remove();
}
}
validateAll() {
let isValid = true;
Object.keys(this.rules).forEach(fieldName => {
if (!this.validateField(fieldName)) {
isValid = false;
}
});
return isValid;
}
handleSubmit(event) {
event.preventDefault();
if (this.validateAll()) {
this.submitForm();
} else {
// 滚动到第一个错误字段
const firstErrorField = this.form.querySelector(".error");
if (firstErrorField) {
firstErrorField.scrollIntoView({ behavior: "smooth", block: "center" });
firstErrorField.focus();
}
}
}
async submitForm() {
// 实现表单提交逻辑
console.log("表单验证通过,准备提交");
}
}
// 使用示例
const validationRules = {
name: [
{ type: "required", message: "姓名是必需的" },
{ type: "minLength", value: 2, message: "姓名至少需要2个字符" }
],
email: [
{ type: "required", message: "邮箱是必需的" },
{ type: "email", message: "请输入有效的邮箱地址" }
],
age: [
{ type: "required", message: "年龄是必需的" },
{ type: "number", message: "年龄必须是数字" },
{ type: "min", value: 0, message: "年龄不能为负数" },
{ type: "max", value: 150, message: "年龄不能超过150" }
],
phone: [
{ type: "pattern", value: "^\\d{3}-\\d{3}-\\d{4}$", message: "电话号码格式应为 XXX-XXX-XXXX" }
]
};
// const validator = new AdvancedFormValidator(
// document.getElementById("validationForm"),
// validationRules
// );动态表单
动态字段添加
javascript
// 动态表单管理器
class DynamicFormManager {
constructor(form) {
this.form = form;
this.fieldCounter = 0;
this.init();
}
init() {
// 添加动态字段按钮
const addButton = this.form.querySelector("[data-add-field]");
if (addButton) {
addButton.addEventListener("click", () => this.addField());
}
}
addField() {
this.fieldCounter++;
const fieldName = `dynamic-field-${this.fieldCounter}`;
const fieldContainer = document.createElement("div");
fieldContainer.className = "dynamic-field";
fieldContainer.innerHTML = `
<input type="text" name="${fieldName}" placeholder="动态字段 ${this.fieldCounter}">
<button type="button" class="remove-field" data-remove-field>×</button>
`;
// 添加到表单
const addButton = this.form.querySelector("[data-add-field]");
this.form.insertBefore(fieldContainer, addButton.parentNode);
// 添加删除事件
const removeButton = fieldContainer.querySelector("[data-remove-field]");
removeButton.addEventListener("click", () => {
fieldContainer.remove();
});
}
// 动态添加复选框组
addCheckboxGroup(name, options) {
const groupContainer = document.createElement("div");
groupContainer.className = "checkbox-group";
options.forEach((option, index) => {
const checkboxId = `${name}-${index}`;
const checkboxContainer = document.createElement("div");
checkboxContainer.innerHTML = `
<input type="checkbox" id="${checkboxId}" name="${name}" value="${option.value}">
<label for="${checkboxId}">${option.label}</label>
`;
groupContainer.appendChild(checkboxContainer);
});
return groupContainer;
}
// 动态添加单选按钮组
addRadioGroup(name, options) {
const groupContainer = document.createElement("div");
groupContainer.className = "radio-group";
options.forEach((option, index) => {
const radioId = `${name}-${index}`;
const radioContainer = document.createElement("div");
radioContainer.innerHTML = `
<input type="radio" id="${radioId}" name="${name}" value="${option.value}">
<label for="${radioId}">${option.label}</label>
`;
groupContainer.appendChild(radioContainer);
});
return groupContainer;
}
// 根据条件显示/隐藏字段
toggleField(fieldName, condition) {
const field = this.form.elements[fieldName];
if (field) {
field.closest(".form-field").style.display = condition ? "block" : "none";
}
}
}
// 使用示例
// const dynamicForm = new DynamicFormManager(document.getElementById("dynamicForm"));条件字段
javascript
// 条件表单字段管理
class ConditionalFields {
constructor(form) {
this.form = form;
this.conditions = new Map();
this.init();
}
init() {
// 监听所有字段的变化
this.form.addEventListener("change", (event) => {
this.checkConditions(event.target);
});
}
// 添加条件规则
addCondition(fieldName, conditionFunction, targetFields) {
this.conditions.set(fieldName, {
condition: conditionFunction,
targets: targetFields
});
}
// 检查条件
checkConditions(changedField) {
const fieldName = changedField.name;
const conditionRule = this.conditions.get(fieldName);
if (conditionRule) {
const shouldShow = conditionRule.condition(changedField, this.form);
this.toggleFields(conditionRule.targets, shouldShow);
}
}
// 切换字段显示状态
toggleFields(fieldNames, show) {
fieldNames.forEach(fieldName => {
const field = this.form.elements[fieldName];
if (field) {
const container = field.closest(".form-field") || field.parentNode;
container.style.display = show ? "block" : "none";
// 如果隐藏字段,清除其值并移除 required 属性
if (!show) {
field.value = "";
field.removeAttribute("required");
}
}
});
}
}
// 使用示例
// const conditionalForm = new ConditionalFields(document.getElementById("conditionalForm"));
// 添加条件规则
// conditionalForm.addCondition("userType", (field) => {
// return field.value === "business";
// }, ["companyName", "businessLicense"]);
// conditionalForm.addCondition("newsletter", (field) => {
// return field.checked;
// }, ["newsletterFrequency"]);表单最佳实践
1. 渐进式增强
html
<!-- 渐进式增强的表单 -->
<form id="progressiveForm" action="/submit" method="POST">
<div class="form-field">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
<div class="error-message" hidden></div>
</div>
<div class="form-field">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<div class="error-message" hidden></div>
</div>
<div class="form-field">
<label for="message">留言:</label>
<textarea id="message" name="message" rows="4"></textarea>
<div class="error-message" hidden></div>
</div>
<button type="submit">提交</button>
<div class="form-message" hidden></div>
</form>javascript
// 渐进式增强 JavaScript
class ProgressiveForm {
constructor(form) {
this.form = form;
this.isEnhanced = false;
this.init();
}
init() {
// 检查是否支持 JavaScript 增强
if (this.supportsEnhancement()) {
this.enhanceForm();
}
}
supportsEnhancement() {
return 'fetch' in window && 'FormData' in window;
}
enhanceForm() {
this.isEnhanced = true;
// 阻止默认提交
this.form.addEventListener("submit", (event) => {
event.preventDefault();
this.handleSubmit();
});
// 添加实时验证
this.form.querySelectorAll("input, textarea").forEach(field => {
field.addEventListener("blur", () => this.validateField(field));
});
console.log("表单已增强");
}
validateField(field) {
if (!this.isEnhanced) return true;
const value = field.value.trim();
let isValid = true;
let errorMessage = "";
if (field.hasAttribute("required") && !value) {
isValid = false;
errorMessage = "此字段是必需的";
} else if (field.type === "email" && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
isValid = false;
errorMessage = "请输入有效的邮箱地址";
}
this.showFieldError(field, errorMessage);
return isValid;
}
showFieldError(field, message) {
const errorElement = field.parentNode.querySelector(".error-message");
if (errorElement) {
errorElement.textContent = message;
errorElement.hidden = !message;
}
field.classList.toggle("error", !!message);
}
async handleSubmit() {
if (!this.isEnhanced) return;
// 验证所有字段
let isValid = true;
this.form.querySelectorAll("input, textarea").forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
if (!isValid) return;
try {
this.setFormState("submitting");
const formData = new FormData(this.form);
const response = await fetch(this.form.action, {
method: this.form.method,
body: formData
});
if (response.ok) {
this.setFormState("success", "提交成功!");
this.form.reset();
} else {
throw new Error("提交失败");
}
} catch (error) {
this.setFormState("error", "提交失败,请稍后重试");
console.error("提交错误:", error);
}
}
setFormState(state, message = "") {
const submitButton = this.form.querySelector("button[type='submit']");
const messageElement = this.form.querySelector(".form-message");
switch (state) {
case "submitting":
submitButton.disabled = true;
submitButton.textContent = "提交中...";
break;
case "success":
submitButton.disabled = false;
submitButton.textContent = "提交";
if (messageElement) {
messageElement.textContent = message;
messageElement.className = "form-message success";
messageElement.hidden = false;
}
break;
case "error":
submitButton.disabled = false;
submitButton.textContent = "提交";
if (messageElement) {
messageElement.textContent = message;
messageElement.className = "form-message error";
messageElement.hidden = false;
}
break;
}
}
}
// 使用示例
// const progressiveForm = new ProgressiveForm(document.getElementById("progressiveForm"));2. 可访问性优化
javascript
// 可访问性友好的表单
class AccessibleForm {
constructor(form) {
this.form = form;
this.init();
}
init() {
// 为所有字段添加 ARIA 属性
this.enhanceAccessibility();
// 添加键盘导航支持
this.form.addEventListener("keydown", (event) => {
this.handleKeyboardNavigation(event);
});
}
enhanceAccessibility() {
this.form.querySelectorAll("input, textarea, select").forEach(field => {
// 确保每个字段都有标签
const label = this.form.querySelector(`label[for="${field.id}"]`);
if (label && !field.hasAttribute("aria-labelledby")) {
field.setAttribute("aria-labelledby", field.id);
}
// 添加描述性 ARIA 属性
if (field.hasAttribute("required")) {
field.setAttribute("aria-required", "true");
}
// 为错误状态添加 ARIA
field.setAttribute("aria-invalid", "false");
});
}
handleKeyboardNavigation(event) {
// Ctrl + Enter 提交表单
if (event.ctrlKey && event.key === "Enter") {
event.preventDefault();
this.form.dispatchEvent(new Event("submit"));
}
// Esc 键清除当前字段
if (event.key === "Escape") {
const activeElement = document.activeElement;
if (activeElement.form === this.form) {
activeElement.value = "";
}
}
}
setFieldError(field, message) {
field.setAttribute("aria-invalid", "true");
field.setAttribute("aria-describedby", `error-${field.name}`);
// 确保错误消息对屏幕阅读器可见
const errorElement = document.getElementById(`error-${field.name}`);
if (errorElement) {
errorElement.setAttribute("role", "alert");
}
}
clearFieldError(field) {
field.setAttribute("aria-invalid", "false");
field.removeAttribute("aria-describedby");
}
}实际应用示例
1. 用户注册表单
javascript
// 用户注册表单管理器
class UserRegistrationForm {
constructor(form) {
this.form = form;
this.init();
}
init() {
// 实时验证
this.form.querySelectorAll("input").forEach(field => {
field.addEventListener("blur", () => this.validateField(field));
field.addEventListener("input", () => this.clearError(field));
});
// 密码强度检查
const passwordField = this.form.elements.password;
if (passwordField) {
passwordField.addEventListener("input", () => this.checkPasswordStrength());
}
// 确认密码验证
const confirmPasswordField = this.form.elements.confirmPassword;
if (confirmPasswordField) {
confirmPasswordField.addEventListener("input", () => this.validatePasswordMatch());
}
// 表单提交
this.form.addEventListener("submit", (event) => this.handleSubmit(event));
}
validateField(field) {
const fieldName = field.name;
const value = field.value.trim();
let errorMessage = "";
switch (fieldName) {
case "username":
if (!value) {
errorMessage = "用户名是必需的";
} else if (value.length < 3) {
errorMessage = "用户名至少需要3个字符";
} else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
errorMessage = "用户名只能包含字母、数字和下划线";
}
break;
case "email":
if (!value) {
errorMessage = "邮箱是必需的";
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
errorMessage = "请输入有效的邮箱地址";
}
break;
case "password":
if (!value) {
errorMessage = "密码是必需的";
} else if (value.length < 8) {
errorMessage = "密码至少需要8个字符";
}
break;
case "confirmPassword":
if (!value) {
errorMessage = "请确认密码";
} else if (value !== this.form.elements.password.value) {
errorMessage = "两次输入的密码不一致";
}
break;
}
this.setError(field, errorMessage);
return !errorMessage;
}
checkPasswordStrength() {
const password = this.form.elements.password.value;
const strengthIndicator = this.form.querySelector(".password-strength");
if (!strengthIndicator) return;
let strength = 0;
let feedback = [];
if (password.length >= 8) strength++;
else feedback.push("至少8个字符");
if (/[a-z]/.test(password)) strength++;
else feedback.push("包含小写字母");
if (/[A-Z]/.test(password)) strength++;
else feedback.push("包含大写字母");
if (/\d/.test(password)) strength++;
else feedback.push("包含数字");
if (/[^a-zA-Z\d]/.test(password)) strength++;
else feedback.push("包含特殊字符");
const strengthText = ["很弱", "弱", "一般", "强", "很强"];
const strengthClass = ["very-weak", "weak", "medium", "strong", "very-strong"];
strengthIndicator.textContent = `密码强度:${strengthText[strength] || "很弱"}`;
strengthIndicator.className = `password-strength ${strengthClass[strength] || "very-weak"}`;
if (feedback.length > 0) {
strengthIndicator.title = "建议:" + feedback.join("、");
}
}
validatePasswordMatch() {
const password = this.form.elements.password.value;
const confirmPassword = this.form.elements.confirmPassword.value;
const confirmPasswordField = this.form.elements.confirmPassword;
if (confirmPassword && password !== confirmPassword) {
this.setError(confirmPasswordField, "两次输入的密码不一致");
} else {
this.clearError(confirmPasswordField);
}
}
setError(field, message) {
field.classList.toggle("error", !!message);
const errorElement = field.parentNode.querySelector(".error-message");
if (errorElement) {
errorElement.textContent = message;
errorElement.hidden = !message;
}
}
clearError(field) {
field.classList.remove("error");
const errorElement = field.parentNode.querySelector(".error-message");
if (errorElement) {
errorElement.textContent = "";
errorElement.hidden = true;
}
}
async handleSubmit(event) {
event.preventDefault();
// 验证所有字段
let isValid = true;
this.form.querySelectorAll("input").forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
if (!isValid) {
const firstError = this.form.querySelector(".error");
if (firstError) {
firstError.focus();
}
return;
}
// 提交表单
await this.submitRegistration();
}
async submitRegistration() {
const submitButton = this.form.querySelector("button[type='submit']");
const originalText = submitButton.textContent;
try {
submitButton.disabled = true;
submitButton.textContent = "注册中...";
const formData = new FormData(this.form);
const userData = Object.fromEntries(formData);
// 移除确认密码字段
delete userData.confirmPassword;
const response = await fetch("/api/register", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
const result = await response.json();
if (response.ok) {
this.showMessage("注册成功!请检查邮箱确认账户。", "success");
this.form.reset();
} else {
this.showMessage(result.message || "注册失败,请稍后重试", "error");
}
} catch (error) {
this.showMessage("网络错误,请稍后重试", "error");
console.error("注册错误:", error);
} finally {
submitButton.disabled = false;
submitButton.textContent = originalText;
}
}
showMessage(message, type) {
const messageElement = this.form.querySelector(".form-message");
if (messageElement) {
messageElement.textContent = message;
messageElement.className = `form-message ${type}`;
messageElement.hidden = false;
setTimeout(() => {
messageElement.hidden = true;
}, 5000);
}
}
}
// 使用示例
// const registrationForm = new UserRegistrationForm(document.getElementById("registrationForm"));2. 多步骤表单
javascript
// 多步骤表单管理器
class MultiStepForm {
constructor(form) {
this.form = form;
this.steps = Array.from(form.querySelectorAll(".form-step"));
this.currentStep = 0;
this.formData = {};
this.init();
}
init() {
// 初始化步骤指示器
this.createStepIndicator();
// 显示第一步
this.showStep(0);
// 添加导航事件
this.form.addEventListener("click", (event) => {
if (event.target.matches("[data-next]")) {
event.preventDefault();
this.nextStep();
}
if (event.target.matches("[data-prev]")) {
event.preventDefault();
this.prevStep();
}
});
// 表单提交
this.form.addEventListener("submit", (event) => {
event.preventDefault();
this.submitForm();
});
}
createStepIndicator() {
const indicator = document.createElement("div");
indicator.className = "step-indicator";
this.steps.forEach((step, index) => {
const stepNumber = index + 1;
const stepElement = document.createElement("div");
stepElement.className = "step";
stepElement.innerHTML = `
<div class="step-number">${stepNumber}</div>
<div class="step-title">${step.dataset.title || `步骤 ${stepNumber}`}</div>
`;
indicator.appendChild(stepElement);
});
this.form.insertBefore(indicator, this.steps[0]);
this.indicator = indicator;
}
showStep(stepIndex) {
// 隐藏所有步骤
this.steps.forEach(step => {
step.hidden = true;
});
// 显示当前步骤
this.steps[stepIndex].hidden = false;
this.currentStep = stepIndex;
// 更新步骤指示器
this.updateStepIndicator();
// 更新导航按钮
this.updateNavigation();
}
updateStepIndicator() {
const stepElements = this.indicator.querySelectorAll(".step");
stepElements.forEach((stepElement, index) => {
stepElement.classList.toggle("active", index === this.currentStep);
stepElement.classList.toggle("completed", index < this.currentStep);
});
}
updateNavigation() {
const prevButton = this.form.querySelector("[data-prev]");
const nextButton = this.form.querySelector("[data-next]");
const submitButton = this.form.querySelector("[type='submit']");
if (prevButton) {
prevButton.hidden = this.currentStep === 0;
}
if (nextButton) {
nextButton.hidden = this.currentStep === this.steps.length - 1;
}
if (submitButton) {
submitButton.hidden = this.currentStep !== this.steps.length - 1;
}
}
nextStep() {
if (this.validateCurrentStep()) {
this.saveCurrentStepData();
if (this.currentStep < this.steps.length - 1) {
this.showStep(this.currentStep + 1);
}
}
}
prevStep() {
if (this.currentStep > 0) {
this.saveCurrentStepData();
this.showStep(this.currentStep - 1);
}
}
validateCurrentStep() {
const currentStep = this.steps[this.currentStep];
const requiredFields = currentStep.querySelectorAll("[required]");
let isValid = true;
requiredFields.forEach(field => {
if (!field.value.trim()) {
field.classList.add("error");
isValid = false;
} else {
field.classList.remove("error");
}
});
return isValid;
}
saveCurrentStepData() {
const currentStep = this.steps[this.currentStep];
const stepData = new FormData(currentStep);
for (let [key, value] of stepData.entries()) {
this.formData[key] = value;
}
}
async submitForm() {
this.saveCurrentStepData();
const submitButton = this.form.querySelector("[type='submit']");
const originalText = submitButton.textContent;
try {
submitButton.disabled = true;
submitButton.textContent = "提交中...";
const response = await fetch("/api/multi-step-submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(this.formData)
});
if (response.ok) {
this.showMessage("提交成功!", "success");
this.form.reset();
this.showStep(0);
} else {
throw new Error("提交失败");
}
} catch (error) {
this.showMessage("提交失败,请稍后重试", "error");
console.error("提交错误:", error);
} finally {
submitButton.disabled = false;
submitButton.textContent = originalText;
}
}
showMessage(message, type) {
const messageElement = document.createElement("div");
messageElement.className = `form-message ${type}`;
messageElement.textContent = message;
this.form.appendChild(messageElement);
setTimeout(() => {
messageElement.remove();
}, 3000);
}
}
// 使用示例
// const multiStepForm = new MultiStepForm(document.getElementById("multiStepForm"));总结
JavaScript 表单处理的核心要点:
- 表单元素访问:通过 ID、表单对象、集合操作访问表单元素
- 事件处理:submit、input、focus、blur、change 等事件
- 数据处理:FormData 对象、手动构建数据、数据转换
- 表单验证:HTML5 内置验证、JavaScript 验证、自定义验证规则
- 动态表单:动态字段添加、条件字段显示、动态表单管理
- 最佳实践:渐进式增强、可访问性优化、用户体验优化
- 实际应用:用户注册表单、多步骤表单、复杂表单管理
掌握表单处理技术是创建交互式 Web 应用程序的基础。在下一章节中,我们将学习 JavaScript 的代码规范。