es-toolkit 兼容层isSafeInteger详解:安全整数判定、类型收窄与原生替代方案
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
isSafeInteger是 es-toolkit 为兼容 lodash 而提供的谓词函数,用于判断一个值是否为"安全整数"(safe integer),即能被 IEEE 754 双精度浮点数精确表示的整数。本文以 isSafeInteger 兼容层文档 为主体,结合 源码实现 与 单元测试,完整讲解它的判定规则、类型收窄能力、与isInteger/isNumber的区别,以及官方给出的原生替代建议。读完你将能在数值校验、边界防御和 TypeScript 类型收窄场景中正确选用该 API。
什么是"安全整数"
JavaScript 的number采用 IEEE 754 双精度浮点数格式,只能精确表示-(2^53 - 1)到2^53 - 1之间的整数,即-9007199254740991到9007199254740991。超出该范围时,相邻整数可能被舍入到同一个值,导致精度丢失。
在 实现源码 的注释中对此有明确说明:安全整数是可以作为number精确表示、且不会有其他整数被舍入到它的整数。判定范围包括两个端点(闭区间)。
快速上手
从 compat 入口导入并调用:
import { isSafeInteger } from 'es-toolkit/compat'; // 安全整数 isSafeInteger(3); // true isSafeInteger(-42); // true isSafeInteger(0); // true isSafeInteger(Number.MAX_SAFE_INTEGER); // true (9007199254740991) isSafeInteger(Number.MIN_SAFE_INTEGER); // true (-9007199254740991) // 超出安全范围的整数 isSafeInteger(Number.MAX_SAFE_INTEGER + 1); // false isSafeInteger(Number.MIN_SAFE_INTEGER - 1); // false isSafeInteger(9007199254740992); // false // 非整数的数值 isSafeInteger(3.14); // false isSafeInteger(Infinity); // false isSafeInteger(-Infinity); // false isSafeInteger(NaN); // false // 非 number 类型 isSafeInteger('3'); // false isSafeInteger(1n); // false (BigInt) isSafeInteger([]); // false isSafeInteger({}); // false isSafeInteger(null); // false isSafeInteger(undefined); // false参数与返回值
value(unknown):待检查的值。- 返回
value is number:当值为安全整数时返回true,否则返回false。
值得注意的是,虽然签名接受任意类型,但任何非number类型(包括BigInt、字符串、对象、null、undefined)都会直接返回false,不会发生隐式转换。
从源码看实现本质
整个实现只有一行:
// src/compat/predicate/isSafeInteger.ts export function isSafeInteger(value: unknown): value is number { return Number.isSafeInteger(value); }它直接委托给原生Number.isSafeInteger。这也解释了两个关键特性:
- 零转换语义:
Number.isSafeInteger会先检查类型是否为number,因此1n、'3'、[]等都不会被"矫正"成数字后再判断。 - 类型收窄:返回类型声明为
value is number,是标准的 TypeScript 类型谓词(type predicate)。
在 src/compat/compat.ts#L234 和 src/browser.ts#L232 中均通过export { isSafeInteger }对外公开,分别支持es-toolkit/compat与浏览器构建两种入口。
TypeScript 类型收窄示例
const value: unknown = 3; if (isSafeInteger(value)) { // 此处 value 已被收窄为 number console.log(value.toFixed(2)); // 类型安全地调用 number 方法 }单元测试 中通过expectTypeOf(value).toEqualTypeOf<number>()验证了这种收窄行为。
测试用例验证的判定边界
isSafeInteger.spec.ts 覆盖了完整的边界场景:
| 输入 | 期望结果 | 依据 |
|---|---|---|
-1、0、1 | true | 整数均为安全整数(spec#L60-L67) |
1.1、3.14 | false | 浮点数不是整数(spec#L17-L20) |
1n | false | BigInt 不参与判定(spec#L22-L25) |
NaN、Infinity、-Infinity | false | 非有限数值(spec#L37-L45) |
Number.MAX_SAFE_INTEGER + 2 | false | 超出上限(spec#L52-L55) |
Number.MIN_SAFE_INTEGER - 2 | false | 低于下限(spec#L47-L50) |
Object(1)、true、new Date()、/x/、symbol、falsey值 | false | 非 number 或包装对象(spec#L70-L95) |
测试还特别验证了1.7976931348623157e308(接近Number.MAX_VALUE)会返回false,因为它远超安全整数上限。
与isInteger、isNumber的区分
compat 层还提供了两个易混淆的兄弟函数,它们的判定粒度不同:
isInteger:仅判断是否为整数,不限制范围。Number.MAX_SAFE_INTEGER + 1会被判为true。实现见 isInteger.ts,直接委托Number.isInteger。isNumber:只要值是number类型(含NaN、Infinity)就返回true,甚至识别new Number(42)包装对象。实现见 isNumber.ts,通过typeof与getTag双重判断。isSafeInteger:三者中最严格,要求"是整数 + 在安全范围内 + 原始 number 类型"。
典型选用规则:需要"任意整数"用isInteger;需要"是数字(含非有限值)"用isNumber;需要"可安全参与整数运算"用isSafeInteger。
在仓库其他模块中的实际应用
Number.isSafeInteger的语义在 es-toolkit 内部也被复用:
- isLength.ts 用
Number.isSafeInteger(value) && value >= 0判断合法的数组长度,因为数组length必须是安全范围内的非负整数。 - times.ts 用
!Number.isSafeInteger(n)提前拒绝超范围的调用次数,避免危险循环。
这说明"安全整数"判定是数值校验体系的基础构件,常用于数组长度、循环次数、索引运算等对精度敏感的防御性检查。
官方建议:优先使用原生Number.isSafeInteger
原文档开篇即给出醒目警告(isSafeInteger.md):
由于额外的类型检查开销,这个
isSafeInteger函数运行较慢。请改用更快、更现代的Number.isSafeInteger。
原因在于 compat 层为了保持与 lodash 的 API 签名(接受any并保持宽松调用方式)而引入的函数包装开销。从 实现源码 可见,最终行为与原生 API 完全一致,因此在新代码中直接使用Number.isSafeInteger(value)即可获得完全相同的判定结果与更高的性能。
只有在以下场景才推荐使用 compat 版本:
- 需要与 lodash 风格的既有代码保持一致,便于迁移;
- 需要
value is number这一类型谓词带来的类型收窄体验(原生Number.isSafeInteger不提供类型收窄); - 在统一封装层中希望通过
es-toolkit/compat单一入口管理所有谓词函数。
总结
isSafeInteger判定-(2^53 - 1)到2^53 - 1)闭区间内的整数,超出即返回false。- 底层直接委托
Number.isSafeInteger,行为一致、无隐式类型转换。 - 返回类型谓词
value is number,可在if分支中安全收窄类型。 - 与
isInteger(不限范围)、isNumber(含非有限值与包装对象)形成三级严格度差异。 - 对性能敏感的新代码,官方建议直接用原生
Number.isSafeInteger。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考