价格字段从表单传过来,用户什么都没填,前端用 !isNaN(value) 判断,返回 true,空字符串被当成 0 存进了库,第二天对账才发现差了几千块。

isNaN 为什么会放行空值

它拿到参数先转成数字,再看是不是 NaN。Number('') 是 0,Number(' ') 是 0,Number(null) 也是 0,所以这些值全被判成数字。换成 Number('12px') 得到 NaN,这种情况反而拦得住。

副作用还有两个:参数是 BigInt 时直接抛 TypeError,是 Symbol 时也抛。业务里塞进来一个奇怪的对象,判断函数自己先崩了。

Number.isNaN 回答的不是这个问题

Number.isNaN 不做类型转换,只有真正的 NaN 才返回 true,空串、null、字符串 '12' 全部返回 false。它回答的是「这个值是不是 NaN」,不是「这个值是不是数字」。很多人拿它做校验,结果所有非 NaN 的值都被放了过去。

真正该用的是 Number.isFinite

Number.isFinite 只对数字类型、并且不是 Infinity 不是 NaN 的值返回 true,其余一律 false。类型和值一起判断,正好是「能不能当数字算」的意思。

输入框里拿到的是字符串,得先 trim 再转,转换前还要卡住空串,否则 Number(' ') 又变成 0。

function isNumber(v) { if (typeof v === 'number') return Number.isFinite(v); if (typeof v !== 'string') return false; const s = v.trim(); if (s === '') return false; return Number.isFinite(Number(s)); }

parseInt 不能当校验用

parseInt('12px') 返回 12,parseFloat('1.2.3') 返回 1.2,这两个函数是「能读多少读多少」,只适合取值,不适合判断。拿它做校验,等于给非法输入开了个后门。

还有进制的坑,parseInt('08') 在老引擎里会被当成八进制解析,现在基本不会了,但显式传个 10 更稳。

结论:接口字段判类型用 typeof 加 Number.isFinite,表单字符串先 trim 再转,isNaN 只留在维护老代码的时候用。