什么,Ajax请求也有精度丢失?chrome把精度吃掉啦?


hello大家好,我是Range。
精度丢失,是JavaScript里经常面临的一个问题,特别是涉及到金钱操作的时候。另外一种场景,就是后端返回某些id值,使用了java语言的 long 类型,在后端是正常的,在浏览器的network里看到后端返回数据也是OK的,但是在JS中拿到的对应number值就是不对,这时候也是精度丢失问题。
作者提到的这个,恰好前年也遇到过,不过并没有想太多,我们是直接和后端约定了,long类型或者雪花ID类型的字段,返回给前端直接转成string来避免精度问题。
但是,我感觉作者给出的JavaScript代码仍然有问题,在axios的 transformResponse 里,假设他的用法是正确的,那么他是先用 JSON.stringify 把接收到的 data 转成string,然后再用 json-bigint 来parse。我的疑问是,如果这里拿到的 data 已经是一个JSON而不是string了,那精度丢失应该已经发生了,这时候再来转换我认为是没用了。我们肯定是只能拿后端返回的原始string,直接用 json-bigint 来parse,才能避免框架内部转JSON时发生的丢失。
我看了下,axios官方有这个issue,作者的方案,应该是我上面描述的逻辑,https://github.com/axios/axios/issues/3440
下面是正文部分。


💥【血泪现场还原】💥

"三年前项目刚搭建时,我就提出过:JS的Number.MAX_SAFE_INTEGER(2^53-1)就是颗定时炸弹,ID建议传输给前端之前格式化为字符串类型。

结果后端大佬们坚持说:'我们Java Long随便传、我们生成的ID就是数字、我们不会有那么多数据',现在用户量剧增,ID突破js最大安全范围,前端拿到的ID超出位全变成了000000这样的鬼样子!"

🕳️【史诗级甩锅现场】🕳️

运维:"接口全炸了!"
产品:"用户投诉页面报错!"
后端(推眼镜):"这是前端精度问题,你们自己处理下,我返回的没问题啊"

😖【原因分析】😖

一、Java 中的 long 类型

  • 「类型」:Java 中的 long 是一个 「有符号的 64 位整数」(signed 64-bit integer)。

  • 「存储方式」:Java 的 long 类型使用 「补码表示法」(two's complement)来表示整数,这是一种在计算机中常用的表示有符号整数的方法。补码表示允许负数和正数使用相同的空间表示。

  • 「数值范围」

    • 最大值:2^63 - 1 = 9,223,372,036,854,775,807
    • 最小值:-2^63 = -9,223,372,036,854,775,808

Java 的 long 类型直接表示 64 位的有符号整数,不存在精度丧失问题,能够表示范围非常大的整数。

二、JavaScript 中的 Number 类型

  • 「类型」:JavaScript 中的 Number 是基于 「IEEE 754 双精度浮点数标准(64-bit double-precision floating point)」 。这个标准用于表示浮动小数点数,并且包含了整数和小数部分。

  • 「存储方式」:JavaScript 的 Number 类型使用 「64 位浮点数」格式来存储数据。64 位格式中的 1 位用于符号位,11 位用于指数部分,剩下的 52 位用于尾数(即有效数字的表示)。这种存储方式使得它不仅可以表示整数,还可以表示小数。

    • 「重要的区别」
      :由于浮点数有 52 位用于尾数(有效数字),JavaScript 的 Number 类型只能精确表示最大为 2^53 - 1 的整数。当数字大于 2^53 - 1 时,它会失去精度,因为有效数字超过了尾数的表示范围。
  • 「数值范围」

    JavaScript Number 类型支持更广泛的数值范围(包括小数),但由于尾数位数的限制,它无法精确表示所有的整数。

    • 最大安全整数:2^53 - 1 = 9,007,199,254,740,991(即 Number.MAX_SAFE_INTEGER)。
    • 最小安全整数:-(2^53 - 1) = -9,007,199,254,740,991(即 Number.MIN_SAFE_INTEGER)。

三、long和Number精度差异

  • 「Java long 类型」:是一个 「64 位有符号整数」,用于表示整数,范围从 -2^63 到 2^63 - 1,精度上没有问题。

  • 「JavaScript Number 类型」:是一个 「64 位双精度浮点数」,能够表示非常大的数,但由于其尾数部分的限制,「最大安全整数」为 2^53 - 1,超过这个值会发生精度丧失。

因此,尽管两者都使用了 64 位的存储空间,它们的使用场景和内部实现差异导致了它们在精度和表示范围上的不同,就会出现在java中合法的数字在js中超出安全范围,导致精度丢失。

🚀【前端自救指南】🚀

虽然正确的做法应该是后端用String传输,但既然要背锅,只能硬着头皮解决:

「transformResponse」

在大多数 Axios 封装的请求方法中,前端接收到后端响应的JSON体中,超长数字由于是字符串格式,所以未丢失精度。虽然 Axios 会自动解析响应数据,但你仍然可以在 transformResponse 中手动处理这种情况,确保接收到的数据不会丢失精度。

例如,在 Axios 请求响应的过程中,你可以拦截并将数字类型的 ID 转换成字符串,或者使用 BigInt 进行转换,也可以使用第三方库 json-bigint 统一处理响应体中所有大数。


// 导入 json-bigint
const JSONbig = require('json-bigint');

// 创建 axios 实例并配置 transformResponse
const axiosInstance = axios.create({
  transformResponse: [(data) => {
    // 使用 json-bigint 解析响应数据,避免精度丢失
    if (data) {
      return JSONbig.parse(JSON.stringify(data)); // 先转为 JSON 字符串再解析
    }
    return data;
  }]
});

// 发起请求
axiosInstance.get('/api/your-api')
  .then(response => {
    // 直接获取 id,已处理为 BigInt 类型
    console.log(response.data.id);
  })
  .catch(error => {
    console.error(error);
  });


💡【血泪总结】💡

  • 所有可能超过15位的ID必须用String传输!
  • 分布式ID建议直接设计为String(雪花ID也不安全!)
  • 联调时要用超长响应值测试边界值!
  • 建议把这篇甩到后端群@全体成员

🎤【灵魂拷问】🎤

"到底是技术无知,还是态度问题?当生产事故发生时,我们真的还要继续玩'前端背锅部'的老梗吗?"

最后

欢迎大家访问我们的刷题网站(https://fe.ecool.fun/)或者小程序 前端面试题宝典 进行刷题,1200多道全网最全的前端面试题,让你一网打尽。近期还有会员卡免费领,全场打折的活动不容错过!刷题会员周卡免费送

有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。

图片

原文链接:https://juejin.cn/post/7471822649709051940