12k
All articles

使用原生 JavaScript 处理表单输入:无需框架

介绍如何通过捕获表单提交、使用 HTML5 约束进行验证,以及借助 FormData API 读取输入值来处理原生 JavaScript 表单。

OpenReplay Team
OpenReplay Team
使用原生 JavaScript 处理表单输入:无需框架

表单是网页用户交互的基础。虽然 React 和 Vue 等框架提供了便捷的抽象,但了解如何使用原生 JavaScript 处理表单能让您完全掌控并消除不必要的依赖。

本指南将带您了解 JavaScript 原生表单处理的所有知识——从选择元素和捕获值到验证输入和处理提交。

关键要点

  • 原生 JavaScript 提供了无需框架即可完成表单处理的所有工具
  • 使用 event.preventDefault() 控制表单提交行为
  • 利用 HTML5 约束验证以最少的代码实现基本验证
  • 通过 inputblur 事件实现实时验证,提升用户体验
  • FormData API 简化了表单值的收集和处理
  • 实现自定义表单验证时始终考虑可访问性

JavaScript 基础表单处理

让我们从一个简单的 HTML 表单开始:

<form id="signup-form">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="username">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email">
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password">
  </div>
  <button type="submit">Sign Up</button>
</form>

选择表单元素

您可以通过多种方式访问表单及其元素:

// 通过 ID 选择(推荐用于特定表单)
const form = document.getElementById('signup-form');

// 使用 querySelector 选择
const form = document.querySelector('#signup-form');

// 访问表单元素集合
const forms = document.forms;
const signupForm = forms['signup-form']; // 通过 id/name
const firstForm = forms[0]; // 通过索引

捕获提交事件

要处理表单提交,请为表单添加事件监听器:

const form = document.getElementById('signup-form');

form.addEventListener('submit', function(event) {
  // 阻止默认的表单提交行为
  event.preventDefault();
  
  // 在此处理表单数据
  console.log('Form submitted!');
});

preventDefault() 方法阻止浏览器提交表单和刷新页面,让您控制接下来发生的事情。

访问表单输入值

有多种方式访问表单输入值:

方法 1:访问单个元素

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // 从单个表单元素获取值
  const username = document.getElementById('username').value;
  const email = document.getElementById('email').value;
  const password = document.getElementById('password').value;
  
  console.log(username, email, password);
});

方法 2:使用 form.elements

elements 属性提供对所有表单控件的访问:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // 通过 elements 集合访问
  const username = form.elements.username.value;
  const email = form.elements.email.value;
  const password = form.elements.password.value;
  
  console.log(username, email, password);
});

方法 3:使用 FormData API

FormData API 提供了一种简洁的方式来捕获所有表单值:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // 从表单创建 FormData 对象
  const formData = new FormData(form);
  
  // 访问单个值
  const username = formData.get('username');
  const email = formData.get('email');
  
  // 转换为普通对象
  const formObject = Object.fromEntries(formData);
  console.log(formObject); // {username: '...', email: '...', password: '...'}
});

使用 HTML5 约束进行表单验证

现代 HTML5 提供了无需 JavaScript 即可工作的内置验证属性:

<form id="signup-form">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required minlength="3">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required minlength="8" 
           pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).*$">
    <small>Password must contain at least 8 characters, including uppercase, lowercase, and numbers</small>
  </div>
  <button type="submit">Sign Up</button>
</form>

常见的验证属性包括:

  • required:字段必须填写
  • minlength/maxlength:文本的最小/最大长度
  • min/max:数字输入的最小/最大值
  • pattern:要匹配的正则表达式模式
  • type="email":验证电子邮件格式
  • type="url":验证 URL 格式

利用约束验证 API

JavaScript 可以访问浏览器的内置验证系统:

form.addEventListener('submit', function(event) {
  // 使用约束验证 API 检查表单是否有效
  if (!form.checkValidity()) {
    // 表单无效 - 让浏览器处理错误显示
    return;
  }
  
  // 如果执行到这里,表单是有效的
  event.preventDefault();
  
  // 处理有效的表单数据
  console.log('Form is valid, processing data...');
});

您也可以检查特定输入的有效性:

const emailInput = document.getElementById('email');

// 检查电子邮件是否有效
if (emailInput.validity.valid) {
  console.log('Email is valid');
} else {
  // 检查特定的验证失败
  if (emailInput.validity.typeMismatch) {
    console.log('Email format is incorrect');
  }
  if (emailInput.validity.valueMissing) {
    console.log('Email is required');
  }
}

自定义验证和错误消息

虽然 HTML5 验证很方便,但您经常需要自定义验证逻辑和错误消息:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  const username = document.getElementById('username');
  const email = document.getElementById('email');
  const password = document.getElementById('password');
  
  // 清除之前的错误消息
  clearErrors();
  
  let isValid = true;
  
  // 自定义用户名验证
  if (username.value.trim() === '') {
    displayError(username, 'Username is required');
    isValid = false;
  } else if (username.value.length < 3) {
    displayError(username, 'Username must be at least 3 characters');
    isValid = false;
  }
  
  // 自定义电子邮件验证
  if (!isValidEmail(email.value)) {
    displayError(email, 'Please enter a valid email address');
    isValid = false;
  }
  
  // 自定义密码验证
  if (password.value.length < 8) {
    displayError(password, 'Password must be at least 8 characters');
    isValid = false;
  } else if (!/[A-Z]/.test(password.value)) {
    displayError(password, 'Password must contain at least one uppercase letter');
    isValid = false;
  }
  
  // 如果有效,处理表单
  if (isValid) {
    console.log('Form is valid, submitting...');
    // 提交表单数据
  }
});

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function displayError(input, message) {
  const formControl = input.parentElement;
  const errorElement = document.createElement('div');
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  formControl.appendChild(errorElement);
  input.classList.add('error-input');
}

function clearErrors() {
  document.querySelectorAll('.error-message').forEach(error => error.remove());
  document.querySelectorAll('.error-input').forEach(input => {
    input.classList.remove('error-input');
  });
}

使用输入事件进行实时验证

为了更好的用户体验,在用户输入时进行验证:

const emailInput = document.getElementById('email');

emailInput.addEventListener('input', function() {
  if (this.value && !isValidEmail(this.value)) {
    this.setCustomValidity('Please enter a valid email address');
  } else {
    this.setCustomValidity('');
  }
});

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

input 事件在用户更改值时触发,而 change 事件在输入失去焦点且值已更改后触发。

处理不同的输入类型

单选按钮

<div>
  <p>Subscription plan:</p>
  <label>
    <input type="radio" name="plan" value="free" checked> Free
  </label>
  <label>
    <input type="radio" name="plan" value="premium"> Premium
  </label>
</div>
// 获取选中的单选按钮值
const selectedPlan = document.querySelector('input[name="plan"]:checked').value;

// 监听变化
document.querySelectorAll('input[name="plan"]').forEach(radio => {
  radio.addEventListener('change', function() {
    console.log('Selected plan:', this.value);
  });
});

复选框

<div>
  <p>Interests:</p>
  <label>
    <input type="checkbox" name="interests" value="javascript"> JavaScript
  </label>
  <label>
    <input type="checkbox" name="interests" value="html"> HTML
  </label>
  <label>
    <input type="checkbox" name="interests" value="css"> CSS
  </label>
</div>
// 获取所有选中的值
function getSelectedInterests() {
  const checkboxes = document.querySelectorAll('input[name="interests"]:checked');
  const values = Array.from(checkboxes).map(cb => cb.value);
  return values;
}

// 在表单提交时
form.addEventListener('submit', function(event) {
  event.preventDefault();
  const interests = getSelectedInterests();
  console.log('Selected interests:', interests);
});

下拉选择框

<div>
  <label for="country">Country:</label>
  <select id="country" name="country">
    <option value="">Select a country</option>
    <option value="us">United States</option>
    <option value="ca">Canada</option>
    <option value="uk">United Kingdom</option>
  </select>
</div>
// 获取选中的值
const country = document.getElementById('country').value;

// 监听变化
document.getElementById('country').addEventListener('change', function() {
  console.log('Selected country:', this.value);
});

完整的表单处理示例

让我们将所有内容整合到一个完整的示例中:

document.addEventListener('DOMContentLoaded', function() {
  const form = document.getElementById('signup-form');
  
  // 添加实时验证
  setupRealTimeValidation();
  
  form.addEventListener('submit', function(event) {
    event.preventDefault();
    
    if (validateForm()) {
      // 创建包含表单数据的对象
      const formData = new FormData(form);
      const userData = Object.fromEntries(formData);
      
      // 这里通常会将数据发送到服务器
      console.log('Submitting user data:', userData);
      
      // 模拟 API 调用
      submitFormData(userData)
        .then(response => {
          showSuccessMessage('Account created successfully!');
          form.reset();
        })
        .catch(error => {
          showErrorMessage('Failed to create account. Please try again.');
        });
    }
  });
  
  function validateForm() {
    // 清除之前的错误
    clearErrors();
    
    let isValid = true;
    const username = form.elements.username;
    const email = form.elements.email;
    const password = form.elements.password;
    
    // 验证用户名
    if (username.value.trim() === '') {
      displayError(username, 'Username is required');
      isValid = false;
    } else if (username.value.length < 3) {
      displayError(username, 'Username must be at least 3 characters');
      isValid = false;
    }
    
    // 验证电子邮件
    if (email.value.trim() === '') {
      displayError(email, 'Email is required');
      isValid = false;
    } else if (!isValidEmail(email.value)) {
      displayError(email, 'Please enter a valid email address');
      isValid = false;
    }
    
    // 验证密码
    if (password.value === '') {
      displayError(password, 'Password is required');
      isValid = false;
    } else if (password.value.length < 8) {
      displayError(password, 'Password must be at least 8 characters');
      isValid = false;
    } else if (!/[A-Z]/.test(password.value) || !/[a-z]/.test(password.value) || !/[0-9]/.test(password.value)) {
      displayError(password, 'Password must include uppercase, lowercase, and numbers');
      isValid = false;
    }
    
    return isValid;
  }
  
  function setupRealTimeValidation() {
    const inputs = form.querySelectorAll('input');
    
    inputs.forEach(input => {
      input.addEventListener('input', function() {
        // 用户开始输入时清除错误
        const errorElement = this.parentElement.querySelector('.error-message');
        if (errorElement) {
          errorElement.remove();
          this.classList.remove('error-input');
        }
      });
    });
    
    // 电子邮件特定验证
    form.elements.email.addEventListener('blur', function() {
      if (this.value && !isValidEmail(this.value)) {
        displayError(this, 'Please enter a valid email address');
      }
    });
  }
  
  // 辅助函数
  function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
  
  function displayError(input, message) {
    const formControl = input.parentElement;
    const errorElement = document.createElement('div');
    errorElement.className = 'error-message';
    errorElement.textContent = message;
    formControl.appendChild(errorElement);
    input.classList.add('error-input');
  }
  
  function clearErrors() {
    document.querySelectorAll('.error-message').forEach(error => error.remove());
    document.querySelectorAll('.error-input').forEach(input => {
      input.classList.remove('error-input');
    });
  }
  
  function showSuccessMessage(message) {
    const messageElement = document.createElement('div');
    messageElement.className = 'success-message';
    messageElement.textContent = message;
    form.parentElement.insertBefore(messageElement, form);
    
    // 3秒后移除
    setTimeout(() => {
      messageElement.remove();
    }, 3000);
  }
  
  function showErrorMessage(message) {
    const messageElement = document.createElement('div');
    messageElement.className = 'error-banner';
    messageElement.textContent = message;
    form.parentElement.insertBefore(messageElement, form);
    
    // 3秒后移除
    setTimeout(() => {
      messageElement.remove();
    }, 3000);
  }
  
  // 模拟 API 提交
  function submitFormData(data) {
    return new Promise((resolve, reject) => {
      // 模拟网络请求
      setTimeout(() => {
        // 演示用90%成功率
        if (Math.random() > 0.1) {
          resolve({ success: true, message: 'User created' });
        } else {
          reject({ success: false, message: 'Server error' });
        }
      }, 1500);
    });
  }
});

表单可访问性考虑

使用 JavaScript 处理表单时,确保它们保持可访问性:

  1. 维护焦点管理:显示错误时,将焦点设置到第一个无效字段
  2. 使用 ARIA 属性:为无效字段添加 aria-invalid="true"
  3. 关联错误消息:使用 aria-describedby 将输入与其错误消息连接
  4. 提供清晰的说明:使用 <label> 元素和描述性错误消息
  5. 支持键盘导航:确保所有表单控件都可以通过键盘访问

可访问错误处理的示例:

function displayError(input, message) {
  const formControl = input.parentElement;
  const errorId = `${input.id}-error`;
  
  const errorElement = document.createElement('div');
  errorElement.id = errorId;
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  
  formControl.appendChild(errorElement);
  
  // 添加可访问性属性
  input.setAttribute('aria-invalid', 'true');
  input.setAttribute('aria-describedby', errorId);
  
  // 将焦点设置到第一个无效输入
  if (!document.querySelector('.error-input')) {
    input.focus();
  }
  
  input.classList.add('error-input');
}

常见问题

如何使用 JavaScript 重置表单?

在表单元素上使用内置的 reset 方法:document.getElementById('myForm').reset();

如何在提交前检查表单是否有效?

使用约束验证 API 的 checkValidity 方法:const isValid = form.checkValidity();

如何异步提交表单数据?

要异步提交表单数据,请将 Fetch API 与 FormData 对象结合使用。首先,从表单创建 FormData 实例。然后使用 fetch 以 POST 方法发送。确保处理响应并捕获提交过程中的任何错误。

如何使用原生 JavaScript 处理文件上传?

要处理文件上传,请访问文件输入元素的 files 属性,获取选中的文件,并在使用 fetch 或其他方法提交之前将其附加到 FormData 对象。

input 和 change 事件有什么区别?

`input` 事件在输入值发生变化时触发,而 `change` 事件仅在输入失去焦点且值自获得焦点以来已更改时触发。

如何使用 JavaScript 动态添加表单字段?

要动态添加表单字段,请使用 document.createElement 创建新的输入元素,设置其类型和名称,然后将其附加到表单中的容器元素。

结论

通过掌握原生 JavaScript 表单处理,您可以在不依赖外部库的情况下完全控制表单。这种方法减少了依赖,提高了性能,并加深了您对网页表单实际工作原理的理解。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.