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

表单是网页用户交互的基础。虽然 React 和 Vue 等框架提供了便捷的抽象,但了解如何使用原生 JavaScript 处理表单能让您完全掌控并消除不必要的依赖。
本指南将带您了解 JavaScript 原生表单处理的所有知识——从选择元素和捕获值到验证输入和处理提交。
关键要点
- 原生 JavaScript 提供了无需框架即可完成表单处理的所有工具
- 使用
event.preventDefault()
控制表单提交行为 - 利用 HTML5 约束验证以最少的代码实现基本验证
- 通过
input
和blur
事件实现实时验证,提升用户体验 - 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 处理表单时,确保它们保持可访问性:
- 维护焦点管理:显示错误时,将焦点设置到第一个无效字段
- 使用 ARIA 属性:为无效字段添加
aria-invalid="true"
- 关联错误消息:使用
aria-describedby
将输入与其错误消息连接 - 提供清晰的说明:使用
<label>
元素和描述性错误消息 - 支持键盘导航:确保所有表单控件都可以通过键盘访问
可访问错误处理的示例:
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');
}
常见问题
在表单元素上使用内置的 reset 方法:document.getElementById('myForm').reset();
使用约束验证 API 的 checkValidity 方法:const isValid = form.checkValidity();
要异步提交表单数据,请将 Fetch API 与 FormData 对象结合使用。首先,从表单创建 FormData 实例。然后使用 fetch 以 POST 方法发送。确保处理响应并捕获提交过程中的任何错误。
要处理文件上传,请访问文件输入元素的 files 属性,获取选中的文件,并在使用 fetch 或其他方法提交之前将其附加到 FormData 对象。
`input` 事件在输入值发生变化时触发,而 `change` 事件仅在输入失去焦点且值自获得焦点以来已更改时触发。
要动态添加表单字段,请使用 document.createElement 创建新的输入元素,设置其类型和名称,然后将其附加到表单中的容器元素。
结论
通过掌握原生 JavaScript 表单处理,您可以在不依赖外部库的情况下完全控制表单。这种方法减少了依赖,提高了性能,并加深了您对网页表单实际工作原理的理解。