原生表单验证仅在submit时触发,非实时校验;需用type="submit"按钮或form.requestSubmit()调用,pattern仅对文本类input生效,setCustomValidity需配空字符串清空,novalidate必须加在form标签上。

表单验证只在 submit 时触发,别指望 input 或 blur 自动校验
原生验证不是实时监听器,它只在 submit 事件发生时才统一检查所有字段。哪怕你给 <input required> 加了属性,用户输错、粘贴非法内容、甚至 JS 直接赋值(el.value = 'xxx'),都不会立刻触发校验——更不会弹提示、加红框或阻断操作。
常见误判:看到输入框没反应,就以为 required 失效了。其实它一直“待命”,只是没到触发时机。
- 必须用
<button type="submit">或<input type="submit">触发提交;type="button"或 JS 调form.submit()会完全绕过验证 -
form.requestSubmit()可替代form.submit(),它会走原生校验流程(注意 Safari 15.4+ 才支持) - 移动端回车键提交也触发验证,但前提是表单结构合法(
<form>包裹 + 正确控件类型)
pattern 正则不生效?先看 type 和锚点
pattern 只对 type="text"、"email"、"tel"、"password" 等文本类输入有效,对 type="number" 完全无效——浏览器压根不拿正则去比对数字输入框的值。
另外,Chrome/Firefox 会自动给 pattern 加 ^ 和 $,所以写 pattern="[0-9]{6}" 就够了;但如果你手动写了 ^[0-9]{6}$,Chrome 会报 Invalid regular expression 错误。
立即学习“前端免费学习笔记(深入)”;
- 想严格校验邮箱,别依赖
type="email"(它连a@b.c都放过),改用type="text" pattern="^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$" - 中文匹配要显式写 Unicode 范围:
pattern="[\u4e00-\u9fa5a-zA-Z0-9]{2,10}" -
title属性必须存在,否则pattern失败时浏览器可能不显示提示文案
自定义错误文案必须配 setCustomValidity("") 清空
setCustomValidity() 不是“设一次就完事”的提示函数,而是设置字段的 validity 状态:传非空字符串 → 字段永久 invalid;传空字符串 "" → 恢复为“可能有效”状态(注意:不是直接变 valid,还得靠后续校验)。
漏掉清空是最常见的死循环坑:用户改对了,但红框和气泡还挂着,checkValidity() 仍返回 false。
- 推荐在
input或blur事件里第一行就调el.setCustomValidity(""),再做逻辑判断 - 不要只在
invalid事件里设错误——它触发时已晚,且不覆盖旧错误 - 异步校验(如用户名是否可用)返回后,必须手动调
el.setCustomValidity("已被注册"),成功时再清空
禁用原生验证时,novalidate 必须加在 form 上
novalidate 是 form 级开关,加在 <button> 或 <input> 上等于没写。加了它,required、pattern、min/max 全部失效,你得自己补全所有逻辑。
低端安卓 WebView 中原生气泡卡顿,这时禁用反而是合理选择,但代价是你得接管:空值判断、格式校验、焦点滚动、可访问性(aria-invalid、aria-describedby)、以及 submit 时的拦截与反馈。
- 正确写法:
<form novalidate></form> - 错误写法:
<button novalidate>提交</button>或<input novalidate> - 禁用后若仍想用
checkValidity()做状态判断,得自己维护每个字段的 validity 对象(比如用 JS 模拟validity.valueMissing)
真正难的不是写对某一行代码,而是理解验证状态如何在用户输入、JS 赋值、浏览器默认行为之间流转。一个 setCustomValidity("") 漏掉,整个表单就卡死;一个 novalidate 加错位置,调试半小时才发现开关根本没起作用。



















