type="number"的min/max不实时报错是因为浏览器仅在change或提交时校验,不拦截输入;需同时满足type="number"、合法数字字符串和显式step才生效。

type="number" 的 min/max 为什么输 105 还不报错?
因为浏览器根本不拦截输入,只在 change 或表单提交时才触发校验。用户粘贴 "999"、输入 "-1000" 或 "3.1415926" 都不会实时阻止。
真正起作用的只有三个条件同时满足:type="number" + 合法数字字符串 + 显式 step。没写 step 时,Chrome/Firefox 默认 step="1" 但允许小数;Safari 可能自动修正为边界值(如输 105 且 max="100",变成 100)。
-
min="0" max="10"不等于“只能输 0–10”,而是“提交时检查是否越界” - 要限制整数,必须写
step="1";要两位小数,写step="0.01";不限小数位用step="any" -
parseFloat(el.value)比Number()更容错,能处理"007"、"1.5 "等带空格或前导零的输入
type="date" 的 min/max 失效?格式和时区是两大雷区
失效几乎从不报错,而是静默降级——日历控件照常显示全部日期,checkValidity() 却返回 false。根本原因是格式不严格。
浏览器只认 "YYYY-MM-DD" 字符串:"2024-03-01" ✅,"2024/03/01" ❌,"2024-3-1" ❌(少补零),"2024-03-01T00:00" ❌(带时间部分)。
- 用
new Date().toISOString().slice(0,10)看似方便,但返回 UTC 时间,东八区用户调用后可能偏差一天 - 安全写法:
const d = new Date(); const isoDate = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}` - 字符串比较逻辑是字典序:
"2024-1-1" > <code>"2024-01-01",导致范围反转,约束完全失效
动态设置 min/max 为什么 UI 不更新?
直接赋值 input.min = "2024-01-01" 在 Safari(尤其 iOS ≤ 15.5)和部分旧版 Chrome 中,UI 日历控件不刷新,用户仍可点选超限日期。
唯一可靠方式是触发 DOM 属性变更:
- ✅ 必须用
input.setAttribute('min', '2024-01-01') - ✅ 或手动触发重绘:
input.dispatchEvent(new Event('input', { bubbles: true })) - ⚠️ 若
input.value已超出新max,浏览器不会自动清空,需显式判断:if (input.value && input.value > input.max) input.value = '' - ⚠️ 联动场景(如结束日期依赖开始日期)中,清空开始日期时,必须同步清空结束日期的
min,否则它会被卡死
服务端为什么必须重复校验?
min 和 max 对后端来说只是建议,不是契约。用户禁用 JS、改 DOM、用 curl 直接 POST,都能绕过所有前端限制。
最易被忽略的是:前端已发现 input.valueAsNumber 返回 NaN,但后端若不做同等解析(比如直接用字符串比对或未做 parseFloat()),照样入库脏数据。
- 服务端校验必须独立于前端逻辑,例如用
parseFloat(req.body.age)再比对范围 - 拒绝非数字字符串(如
"12a")、空值、null、undefined - 日期字段不能只截取字符串,要解析成时间对象再比对,避免时区或格式陷阱
min/max 只拦住粗心用户,拦不住有意绕过的人,也拦不住跨浏览器兼容性缺口。











