Skip to content

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 表单处理的核心要点:

  1. 表单元素访问:通过 ID、表单对象、集合操作访问表单元素
  2. 事件处理:submit、input、focus、blur、change 等事件
  3. 数据处理:FormData 对象、手动构建数据、数据转换
  4. 表单验证:HTML5 内置验证、JavaScript 验证、自定义验证规则
  5. 动态表单:动态字段添加、条件字段显示、动态表单管理
  6. 最佳实践:渐进式增强、可访问性优化、用户体验优化
  7. 实际应用:用户注册表单、多步骤表单、复杂表单管理

掌握表单处理技术是创建交互式 Web 应用程序的基础。在下一章节中,我们将学习 JavaScript 的代码规范。

本站内容仅供学习和研究使用。