先记住这个答案
浏览器内置了一套叫「约束验证」的机制。你在 <input> 或 <textarea> 上写 required、pattern、maxlength 这些属性,用户点提交按钮时浏览器会逐个检查。第一个不合法的控件会获得焦点,旁边弹出一段默认提示文字,同时 submit 事件不会触发。整个过程不需要 JavaScript 参与。
required管空值,pattern管格式patternMismatch随输入更新,默认气泡通常提交时出现novalidate或submit()跳过校验- 浏览器校验只防误填,防不了恶意提交,服务端校验不能省
每个属性管什么
required 最简单,控件值为空就不通过。<input type="text" required> 用户不填就提交不了。
pattern 接受一个正则表达式,浏览器会拿控件的值去匹配。注意浏览器会自动在两端加上 ^ 和 $,所以写 pattern="[0-9]{6}" 就是精确匹配 6 位数字,不用自己加锚点。pattern 只在控件有值时才校验,空值不报错,想同时拦空值要配合 required。
maxlength 的行为和前两个不同:浏览器通常会在用户输入时限制长度。脚本直接给 value 赋超长内容却不会触发 tooLong 约束,调用 checkValidity() 也不会替你兜底。这正是服务端必须独立校验长度的一个例子。
<form>
<label>手机号
<input type="tel" required pattern="1[3-9]\d{9}"
title="请输入 11 位手机号">
</label>
<label>昵称
<input type="text" required maxlength="20"
minlength="2">
</label>
<label>邀请码(选填)
<input type="text" pattern="[A-Z0-9]{8}"
title="8 位大写字母或数字">
</label>
<button type="submit">注册</button>
</form>手机号同时用了 required 和 pattern,空值和格式不对都会被拦。邀请码只有 pattern 没有 required,不填可以提交,填了就必须符合格式。title 可以为输入规则提供提示,但各浏览器错误气泡的文案和呈现并不一致,重要说明应放在控件旁边。
用 JavaScript 扩展验证
浏览器默认气泡不便统一设计;若要在字段旁显示自己的文案,可以先关闭表单的交互式验证,在提交时主动检查,并把提示写到页面上。需要替换浏览器自身的验证消息时,才用 setCustomValidity(),错误修正后还要用空字符串清除旧消息。
<form id="myForm" novalidate>
<input id="phone" type="tel" required pattern="1[3-9]\d{9}">
<span id="phone-error"></span>
<button type="submit">提交</button>
</form>
<script>
const form = document.getElementById('myForm')
const phone = document.getElementById('phone')
const error = document.getElementById('phone-error')
form.addEventListener('submit', e => {
error.textContent = '' // 每次提交前清掉旧提示
if (!form.checkValidity()) {
e.preventDefault()
// validity 对象告诉你具体哪条没过
if (phone.validity.valueMissing) error.textContent = '请填写手机号'
else if (phone.validity.patternMismatch) error.textContent = '手机号格式不对'
}
})
</script>表单加了 novalidate 后浏览器不再自动校验,提交时手动调 checkValidity() 来判断。每个控件的 validity 对象有十来个布尔字段,valueMissing 对应 required 没过,patternMismatch 对应 pattern 没过,tooLong 对应 maxlength 没过,可以根据不同的失败原因给不同提示。
浏览器校验和服务端校验的分工
浏览器校验只能防误填,防不了恶意提交。用户在 DevTools 里删掉 required 属性,或者直接用 curl 发请求,前端校验形同虚设。所以服务端必须再校验一遍,两边的规则保持一致。
浏览器校验的价值在于用户体验:不用等请求往返就能即时反馈,减少无效提交。服务端校验的价值在于安全:防止绕过前端直接提交脏数据。两层缺一不可。
容易答错的地方
- 以为
pattern的状态只在提交时更新 patternMismatch和 CSS 的:invalid会随当前值变化;只是浏览器的默认错误气泡通常在提交等交互式验证时出现。想在用户输入过程中显示自己的错误文案,可监听input并读取validity,还应避免用户未完成输入时就反复打断。- 把
maxlength和max搞混 maxlength限制的是字符串长度,用在<input type="text">和<textarea>上。max限制的是数值大小,用在<input type="number">上。写反了不会报错但也不会生效。
面试官还会怎么问?
type="email" 和 type="url" 自带验证吗?
自带。浏览器会按内置的格式规则校验,不用再写 pattern。但内置的 email 校验比较宽松,比如 a@b 也能通过,如果产品要求更严格的格式,还得加 pattern 或服务端校验。
CSS 的 :valid 和 :invalid 是怎么配合验证属性工作的?
浏览器会根据约束验证的结果实时切换这两个伪类。控件当前值合法就匹配 :valid,不合法就匹配 :invalid。配合 required,一个空的必填框一打开页面就是 :invalid 状态,如果不想一上来就显示红框,可以用 :user-invalid(需要用户交互后才生效)。
用 JavaScript 调 form.submit() 会触发验证吗?
不会。form.submit() 会跳过约束验证直接提交。想在 JS 里提交又想走验证流程,应该用 form.requestSubmit(),它和用户点提交按钮的行为一致。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。