表单验证是网页交互中最基础也最重要的功能之一。用户注册时的用户名格式、密码强度,提交订单时的手机号、收货地址,这些数据如果不经过校验就直接提交到后端,轻则产生脏数据,重则带来安全隐患。用JavaScript在浏览器端做第一道拦截,既能提升用户体验,也能减轻服务器压力。这篇文章从基础的事件监听讲起,逐步实现一套完整可用的前端表单验证方案。

一、表单验证的基本思路与事件选择
前端表单验证的核心逻辑其实很简单:在用户输入或提交的过程中,获取表单元素的值,按照预先定义的规则逐条检查,不满足规则就给出提示。真正需要花心思设计的是触发时机。常用的监听方式有三种:submit事件、blur事件和input事件。
submit事件绑定在form元素上,用户点击提交按钮时统一校验所有字段。这种方式实现简单,逻辑集中,但缺点是反馈滞后,用户填完一大串内容点提交才发现第一个输入框有问题,体验较差。blur事件绑定在输入框上,在元素失去焦点时校验该字段,反馈及时且不会在用户输入过程中频繁打扰。input事件则在每次内容变化时触发,适合做实时提示,比如密码强度条,但如果直接用来做错误提示,用户每敲一个字符就弹一次错误,反而让人烦躁。
比较推荐的做法是组合使用:输入框失焦时校验单个字段,表单提交时整体校验一遍兜底。下面是一个基础的监听结构示例:
const form = document.getElementById('regForm');
const username = document.getElementById('username');
// 失焦时校验单个字段
username.addEventListener('blur', function () {
validateUsername(this.value);
});
// 提交时整体校验
form.addEventListener('submit', function (e) {
if (!validateAll()) {
e.preventDefault(); // 阻止表单默认提交
}
});注意preventDefault这一步,很多初学者忘记阻止默认行为,结果校验失败了表单还是提交出去了,页面直接刷新,提示信息一闪而过,看起来像校验失效了一样。
二、常用校验规则与正则表达式写法
有了触发时机,接下来就是定义校验规则。用户名、密码、手机号、邮箱这几类字段的正则表达式几乎是每个项目的标配,值得整理成可复用的规则库。
用户名一般要求以字母开头,允许字母数字下划线,长度在4到16位之间。密码至少包含字母和数字两种字符,长度不低于6位。手机号是国内场景最常见的校验,目前以1开头的11位数字。邮箱的规则相对复杂,完全符合RFC标准的正则非常冗长,前端校验用简化版即可,严格的格式检查交给后端。对应的正则如下:
const rules = {
// 用户名:字母开头,4-16位字母数字下划线
username: /^[a-zA-Z]\w{3,15}$/,
// 密码:至少6位,必须包含字母和数字
password: /^(?=.*[a-zA-Z])(?=.*\d).{6,}$/,
// 手机号:1开头的11位数字
phone: /^1[3-9]\d{9}$/,
// 邮箱:简化版格式
email: /^[\w.-]+@[\w-]+(\.[\w-]+)+$/
};其中密码的正则用到了前瞻断言,(?=.*[a-zA-Z])表示后面必须存在至少一个字母,(?=.*\d)表示必须存在至少一个数字,两个断言配合.{6,}就实现了组合条件的校验。如果还想要求密码必须包含大写字母和特殊字符,按同样方式追加断言即可。
需要提醒一点,正则不是越严格越好。比如有些开发者会写一个超长的邮箱正则去匹配所有合法情况,结果把一些真实存在但格式少见的邮箱判为非法。前端校验的原则是拦截明显的输入错误,而不是追求百分之百的格式精确,这一点和后端校验的定位完全不同。
除了格式校验,还有一类逻辑校验也很常见,例如注册表单里的确认密码。它没有固定正则,只需要比较两次输入是否一致,通过简单的字符串比对就能实现。再比如必填项检查,用trim()去掉首尾空格后判断长度即可,避免用户只敲了几个空格就通过校验。
三、完整实例:带实时提示的注册表单验证
下面把前面的内容整合起来,实现一个完整的注册表单验证。效果是:失焦时校验对应字段并显示错误提示,提交时统一检查,全部通过才允许提交。提示信息用一个span元素展示,校验失败加红色样式,成功则清除提示。
<form id="regForm">
<p>
用户名:<input type="text" id="username" name="username">
<span class="tip" id="usernameTip"></span>
</p>
<p>
密码:<input type="password" id="password" name="password">
<span class="tip" id="passwordTip"></span>
</p>
<p>
确认密码:<input type="password" id="confirmPwd">
<span class="tip" id="confirmPwdTip"></span>
</p>
<p>
邮箱:<input type="text" id="email" name="email">
<span class="tip" id="emailTip"></span>
</p>
<button type="submit">注册</button>
</form>
<script>
const rules = {
username: /^[a-zA-Z]\w{3,15}$/,
password: /^(?=.*[a-zA-Z])(?=.*\d).{6,}$/,
email: /^[\w.-]+@[\w-]+(\.[\w-]+)+$/
};
function showTip(id, msg) {
const tip = document.getElementById(id);
tip.textContent = msg;
tip.style.color = msg ? 'red' : '';
}
function checkUsername() {
const val = username.value.trim();
if (!val) { showTip('usernameTip', '用户名不能为空'); return false; }
if (!rules.username.test(val)) {
showTip('usernameTip', '字母开头,4-16位字母数字下划线');
return false;
}
showTip('usernameTip', '');
return true;
}
function checkPassword() {
const val = password.value;
if (!rules.password.test(val)) {
showTip('passwordTip', '至少6位,须包含字母和数字');
return false;
}
showTip('passwordTip', '');
return true;
}
function checkConfirm() {
if (confirmPwd.value !== password.value || !confirmPwd.value) {
showTip('confirmPwdTip', '两次密码输入不一致');
return false;
}
showTip('confirmPwdTip', '');
return true;
}
function checkEmail() {
const val = email.value.trim();
if (!rules.email.test(val)) {
showTip('emailTip', '邮箱格式不正确');
return false;
}
showTip('emailTip', '');
return true;
}
username.addEventListener('blur', checkUsername);
password.addEventListener('blur', checkPassword);
confirmPwd.addEventListener('blur', checkConfirm);
email.addEventListener('blur', checkEmail);
document.getElementById('regForm').addEventListener('submit', function (e) {
// 全部通过才提交
const ok = [checkUsername(), checkPassword(), checkConfirm(), checkEmail()].every(Boolean);
if (!ok) e.preventDefault();
});
</script>这段代码的关键点在于把每个字段的校验封装成独立函数,返回布尔值,这样blur事件和submit事件可以复用同一套逻辑,避免重复代码。提交时用every(Boolean)判断所有校验是否都通过,写法简洁且语义清晰。
四、进阶:原生Constraint Validation API与框架方案对比
除了手写JavaScript,浏览器其实内置了一套校验机制,也就是Constraint Validation API。给<input>加上required、pattern、minlength等属性,浏览器会自动完成基础校验,还可以通过setCustomValidity方法自定义错误提示:
const input = document.getElementById('phone');
input.addEventListener('input', function () {
if (!/^1[3-9]\d{9}$/.test(this.value)) {
this.setCustomValidity('请输入正确的手机号');
} else {
this.setCustomValidity(''); // 清空后才能通过校验
}
});这套API的优点是省代码,浏览器连提示气泡都帮你画好了。缺点也明显:提示样式各浏览器表现不一致,难以深度定制,复杂的联动校验(比如确认密码)写起来反而别扭。所以实际项目里,简单场景可以直接用HTML属性校验,对UI有要求或逻辑复杂时还是手写更可控。
如果项目本身用了Vue或React,优先考虑用框架的响应式特性来做校验,配合成熟的库(比如Vue生态的VeeValidate、React生态的Formik加Yup),状态管理和错误展示会更顺手。但无论技术栈怎么变,校验规则本身的写法、正则的构成、触发时机的权衡,这些底层思路都是相通的。最后再强调一句:前端验证只是提升体验的手段,出于安全考虑,后端永远要做一次独立校验,绝不能因为前端校验过了就信任提交的数据。
JS表单验证前端表单验证JavaScript正则验证修改时间:2026-09-04 11:58:48