JavaScript 数字输入校验:parseInt 读到一半,也会给你一个整数

10-01 3阅读

输入多了一段,程序却像没看见

一个数量字段收到十二后面带单位的文本,程序用 parseInt 转换,结果得到十二,看起来很方便。可是如果这段尾巴本来代表录入错误,用户就失去了纠正机会。函数成功返回数字,只能说明开头存在可解析内容,不能证明整条输入符合数量字段的要求。

parseInt 按指定进制从前面解析整数,遇到不能继续解释的字符就停止。Number 对字符串采用整体数值转换,遇到不符合数值语法的尾巴会得到 NaN,但它也接受空白、指数和某些进制前缀。因此改用 Number 仍不等于建立了业务需要的十进制整数校验。

先观察转换,再定义输入语言

下面代码可保存为 JavaScript 文件并用 Node.js 运行,本文在二十四版验证,只依赖内置断言模块。第一组对照单位后缀、指数、小数、空白和十六进制前缀,让两种转换的差别直接显示出来;输出 NaN 时保留文字,方便人工核对。

第二组定义一个具体数量规则:只接收字符串,允许零和不带前导零的正整数,不接受符号、空白、换行、单位或全角数字,最大一百万。先检查字符和长度,再转换并检查范围。规则可以调整,但每一项都应该来自字段契约,而不是偶然的转换结果。

JavaScript 数字输入校验:parseInt 读到一半,也会给你一个整数

AI概念配图,非真实界面

const assert = require('node:assert/strict');

for (const text of ['12px', '1e2', '12.9', ' ', '0x10']) {
  console.log(JSON.stringify(text), parseInt(text, 10), Number(text));
}
assert.equal(parseInt('12px', 10), 12);
assert.ok(Number.isNaN(Number('12px')));
assert.equal(Number(''), 0);
assert.equal(Number(true), 1);

function quantity(text) {
  if (typeof text !== 'string') throw new TypeError('string required');
  if (text.length === 0 || text.length > 7 || /[^0-9]/u.test(text)) {
    throw new RangeError('decimal digits required');
  }
  if (text.length > 1 && text[0] === '0') {
    throw new RangeError('leading zero');
  }
  const value = Number(text);
  if (!Number.isSafeInteger(value) || value > 1_000_000) {
    throw new RangeError('quantity out of range');
  }
  return value;
}
assert.deepEqual(['0', '12', '1000000'].map(quantity), [0, 12, 1000000]);
const rejected = ['', ' ', '12\n', '12px', '1e2', '12.0',
                  '+12', '-1', '012', '12', '1000001', 12, true];
for (const value of rejected) assert.throws(() => quantity(value));
console.log('valid quantities:', [0, 12, 1000000].join(','));
console.log('rejected cases:', rejected.length);
console.log('numeric input checks passed');

整体转换也有自己的宽松入口

Number 将空字符串和只含空白的字符串转换为零,这可能把“尚未填写”变成“明确填写零”。它也能把指数写法解释为数值,而十进制 parseInt 遇到字母就停止。因此选择函数时要先回答空白、指数和单位是否允许,不能笼统地说某一个函数永远更严格。

显式给 parseInt 传入十进制参数,可以避免不必要的进制猜测,却不会让它拒绝尾部字符。对小数字符串,它仍可能取到前面的整数部分。若需求是对已经得到的数字向零截断,应使用明确的数学操作,并先检查数值有效性,而不是借字符串解析绕一圈。

示例检查任意非零到九字符,因此末尾换行也会被拒绝。它没有调用 trim,这表示输入必须原样符合规则。若表单允许自动清理首尾空白,可以在界面层做出明确提示并保留一致的服务端约定,不要让前后端对同一个输入得出不同答案。

类型、范围与缺失值各自验收

函数首先检查字符串类型,因为 Number 会对其他类型进行转换,布尔值也可能变成整数。对于来自 JSON 的真正数值字段,应该另写接受数字的入口,而不是把所有对象都隐式转成字符串。输入类型本身就是接口的一部分,过度容错常把上游错误藏得更深。

安全整数检查说明结果能否在 Number 的整数范围内可靠表示,业务上限则说明这个数量是否允许。本文上限较小,仍把两者都写出来,方便以后变更。若将来需要更长的精确标识,应重新设计类型和传输形式,不能只是把长度上限随手增大。

前导零是否允许也要明确。商品数量通常可以统一表示,而邮政编码、编号等看似数字的字符串可能必须保留前导零,根本不该转换成数值。看到一串数字之前,先确认它用来计算还是用来识别,才能避免把不同标识误合并为同一个数。

保留的测试应覆盖零、上限、超限、空白、小数、指数、尾部单位与非字符串。转换函数负责得到数值,校验函数负责执行契约,展示层再负责格式。这样出现拒绝时就能告诉用户具体违反哪条规则,也能在需求改变后有针对性地修改测试。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。