约束校验:八个 validity flag 与校验 API
WHATWG「Constraint validation」给每个 form control 定义了一组约束(来自 required / pattern / min / max / step / minlength / maxlength / type),并把当前是否违反约束实时反映在控件的
el.validity(ValidityState)上——一组只读布尔标志位。提交前浏览器会自动跑一遍校验,脚本也能用 checkValidity() / reportValidity() / setCustomValidity() 主动驱动。本页把每个 flag 单独点亮一遍,并对照三个 API
的差异。约束的来源有哪些 type 可用,见 input 的 type states;整张表单用 novalidate 关闭校验,见 novalidate。
1 · 逐字段:边输边看哪个 flag 被点亮
下面四个字段各带不同约束。每个右侧实时遍历 el.validity 的全部 key,把当前为 true 的 flag 高亮——valueMissing / typeMismatch / patternMismatch / rangeUnderflow / rangeOverflow / stepMismatch /
tooShort / tooLong / badInput / customError,以及汇总位 valid。同时显示 validationMessage(浏览器给出的本地化文案)与命中的伪类。注意输入框用了 :invalid(红边,一上来空 required 就红)与
:user-invalid(红色光晕,只在交互后才出现)两种描边。
pattern 对整串锚定全匹配。pattern="\d{6}" 等价于浏览器把它包成 ^(?:\d{6})$ 再 test——必须整个值匹配,不是部分匹配,所以无需自己写 ^ / $(写了反而可能因 multiline 语义出问题)。空值不触发
patternMismatch(留给 required 管),要强制非空请配合 required。
2 · setCustomValidity:把业务规则塞进同一套机制
内建约束管不到的业务规则(如「两次密码一致」「用户名已占用」),用 el.setCustomValidity(msg) 注入:传非空字符串 → 该控件 validity.customError 变 true、el.validity.valid 变 false,且这条 msg 成为
el.validationMessage(也是 reportValidity() 气泡里显示的文案);传 "" 清除。下面对同一个框设 / 清自定义错误,看 customError 与文案的变化。
3 · checkValidity vs reportValidity:返回 bool,还是顺带弹气泡
两个 API 都对控件(或整张 form)跑一遍校验并返回 boolean;无效时都会向每个无效控件派发一个 cancelable 的 invalid 事件。区别只有一处:reportValidity() 会额外把焦点移到第一个无效控件并弹出原生气泡;checkValidity()
是「静默」版,只返回结果、不打扰用户(适合脚本里先探一下)。下面对「逐字段」一节的同一张表单分别调用,invalid 事件计数会累加;点 reportValidity 还会看到原生气泡。
| validity flag | 什么时候为 true |
|---|---|
valueMissing |
控件带 required 且值为空(对 checkbox / radio 是未选中,对 select 是选了空 value 的项)。 |
typeMismatch |
值不符合 type 的语法:type=email 不是合法邮箱、type=url 不是合法 URL。 |
patternMismatch |
值非空且不满足 pattern(整串锚定全匹配)。 |
tooShort |
值的长度小于 minlength。仅当用户「编辑过」才参与(脚本设值不算)。 |
tooLong |
值的长度大于 maxlength。用户键入会被 UA 直接截断,故通常只在脚本设值时才点亮。 |
rangeUnderflow |
数值型(number / range / date 等)的值小于 min。 |
rangeOverflow |
数值型的值大于 max。 |
stepMismatch |
数值不落在 step 的整数倍格点上(基准为 min 或 step base)。 |
badInput |
UA 无法把用户输入转换成值:如 type=number 框里残留非数字字符,此时 el.value 读出来是 ""。 |
customError |
setCustomValidity() 设过非空字符串且未清除。 |
| valid | 汇总位:以上九个全为 false 时才为 true——即该控件通过全部约束。 |
跑校验 / 派发 invalid |
返回值 | 移焦 + 弹原生气泡 | 改 customError | |
|---|---|---|---|---|
checkValidity() |
是 | boolean |
否 | 否 |
reportValidity() |
是 | boolean |
是 | 否 |
setCustomValidity(s) |
否(只置位) | void |
否 | 是(非空→customError;""→清除) |
4 · 同一份逻辑里的几件事
<!-- 约束写在 attribute 上 -->
<input name="zip" required pattern="\d{6}" />
<input name="qty" type="number" min="2" max="8" step="2" />
el.validity.valueMissing // required 且空
el.validity.patternMismatch // 值非空且不满足 pattern(整串全匹配)
el.validity.stepMismatch // 不落在 step 格点上
el.validity.valid // 汇总:以上全为 false 才 true
// 业务规则注入
el.setCustomValidity("用户名已被占用"); // → customError = true
el.setCustomValidity(""); // 清除
el.validationMessage; // 当前要显示的文案
// 静默校验 vs 弹气泡
const ok = form.checkValidity(); // 只返回 bool,派发 invalid 事件
form.reportValidity(); // 同上 + 移焦 + 弹原生气泡
// 这个控件会被校验吗?
el.willValidate; // disabled / readonly / type=hidden / datalist 后代 → false
:invalid 一上来就红,:user-invalid 交互后才红。一个空的 required 字段从渲染那一刻就匹配 :invalid——若直接拿它描红边,用户还没动表单就满屏报错,体验很差。:user-invalid /
:user-valid 是为此设计的:只有当用户已经交互过(编辑并失焦,或尝试提交)且仍无效时才匹配。生产里给字段上色应优先用 :user-invalid,把 :invalid 留给「需要持续标记」的少数场景。
关闭校验的两个开关。<form novalidate> 让整张表单提交时跳过约束校验(但 el.validity 和手动 checkValidity() 仍照常工作,本页表单正是 novalidate 才不会在按钮触发提交);单个 submit 按钮上的
formnovalidate 则局部覆盖——让「保存草稿」这种按钮绕过校验、而「正式提交」仍校验。详见 novalidate。
willValidate:这个控件到底参不参与校验。并非所有控件都会被校验。当控件是 disabled、readonly、type=hidden、<button> / output / 这类 non-listed,或位于 <datalist> 后代中时,el.willValidate
为 false——它的约束被完全忽略,既不进自动校验、checkValidity() 也跳过它。判断「这个框现在会不会被校验」看 willValidate,而不是看它有没有写约束属性。