先说几个核心判断。HTML 的表单验证属性,必须老老实实嵌套在 标签里,并且只能用 type="submit" 的按钮来触发。脱离了这两个前提,你编写的那些验证规则,浏览器根本不会执行。

required 不起作用?先看是不是丢在 外面
说实话,添加了 required 属性却没有任何反应,十个里面有九个是因为输入控件压根没有被放在 标签中,或者是提交按钮的 type 属性被错误地设置成了 "button"。浏览器的原生验证机制只在标准的 submit 事件中去检查 required 属性,不需要编写任何 JavaScript 代码就能正常运行——但前提是你的 HTML 结构必须正确无误。
必须是的直接子元素,或者是嵌套在其中的子元素。- 触发提交的按钮必须是
或者。如果使用了type="button",点击后不会触发任何验证。 - 如果用 JavaScript 调用
form.submit(),验证会正常触发;但如果使用element.click()模拟点击一个type="button"的按钮,验证是不会生效的。 - 注意,即使某个字段设置了
display: none被隐藏了,只要它身上有required属性,仍然会参与校验。如果想跳过验证,必须使用hidden属性,或者直接将其从 DOM 中移除。
pattern 正则写不对?记住它自动加 ^$ 且不支持 flag
还有一个很多人踩过的坑。pattern 只对 type="text"、"email"、"tel" 这些文本类型的 input 有效。如果你将它用在 type="number" 上,浏览器会直接忽略。而且,浏览器会在你写的正则表达式外面自动添加 ^...$,如果自己又写了 ^ 或 $,就会报出 Invalid regular expression 的错误。
- 想限制输入 6 到 12 位的字母数字:正确的写法是
pattern="[a-zA-Z0-9]{6,12}"。千万不要写成^[a-zA-Z0-9]{6,12}$。 - 如果想排除以数字开头的情况,可以这样写:
pattern="(?!\d)[a-zA-Z0-9]{6,12}"。注意,在 JavaScript 字符串中,反斜杠需要写成双反斜杠。 - 要匹配中文,必须显式写出 Unicode 范围:
pattern="[\u4e00-\u9fa5a-zA-Z0-9]{2,10}"。\w是无法匹配汉字的。 - 移动端的软键盘也会响应
pattern的内容。如果你写[0-9],它会弹出数字键盘;但即使写了[a-z],也无法保证能弹出英文键盘,这取决于不同浏览器的自身行为。
min/max 在 number 和 date 上表现完全不同
说到 min 和 max,这两个属性表面看起来一模一样,实际行为差异很大。对于 type="number" 而言,它是实时拦截的——如果你设置了 min="10",用户试图输入一个 9,会被直接阻止。但对于 type="date",它只会在提交的那一瞬间进行校验,而且 step 属性对日期选择器是无效的。
- 当
type="number" min="10"时,用户不仅无法手动输入小于 10 的值,即使从别处复制一个 "5" 粘贴进去,也会被立刻清空。 - 对于
type="date" min="2023-01-01",即使你选择一个看似离谱的 "9999-99-99",浏览器也不会报错——它根本不校验日期的合法性。它只检查字符串格式是否为标准的 ISO 格式。如果传入了 "2023-1-1" 或 "2023/01/01" 这种格式,解析会失败,value会变为空,从而触发valueMissing错误。 step="any"允许输入任意小数;而step="0.1"并不意味着“最多只能保留一位小数”,如果你填写了1.01,照样会校验失败。type="date"的min和max值必须是完整的YYYY-MM-DD格式,少一位都会导致校验无法正常工作。
setCustomValidity 后忘了清空?字段就永远 invalid
这是最常见,也是最容易被忽略的坑——setCustomValidity。只要 setCustomValidity("xxx") 的参数是一个非空字符串,那么这个字段就会永久性地被判定为无效。即使用户后续已经将内容修改正确,表单也始终无法提交。
- 每次用户输入内容后,都必须手动调用一次
input.setCustomValidity("")来清空错误状态。否则,之前的错误信息会一直残留。 - 正确的做法是在
input或blur事件中监听并重置错误。切不可只在invalid事件里设置错误提示,却忘记事后清理。 - 对于有多个关联字段的校验,比如密码确认,两个输入框需要分别调用
setCustomValidity去设置错误。如果只在一个上面设置错误,另一个仍然能够通过验证。 - 如果使用
event.preventDefault()拦截了invalid事件,那么必须配合setCustomValidity一起使用,否则字段会一直卡在 invalid 状态,无法恢复。
归根结底,原生验证机制只关注提交那一瞬间的状态,它不会监控用户输入过程中的任何变化。无论是 pattern、min 还是 required,它们都依赖于用户最终触发 submit 事件。因此,请务必记住:服务端的校验,一步都不能省略。
