Javascript 提案 BigInt 的一些坑

昨天译了一篇文章:BigInt:JavaScript 中的任意精度整数。很多人在评论去吐槽,也有很多人给我发微信问我一些问题。于是我又抽空总结了一下 BigInt 的那些坑。

BigInt 从 V8 的 6.7 版本开始支持,对应的 Chrome 版本是 67。最新发布的 Node.js 10 内置的是 V8 6.6,所以 BigInt 不能在 Node.js 10 中使用。

1. 定义形式

BigInt 使用数字字面量加 n 表示支持二进制、八进制、十六进制形式。

对于八进制,只支持新写法 0o064n,不支持旧的写法 0640。(注意区分数字“零”和字幕“O”)

  • 普通写法:
1n
  • 十六进制:
0x6n
0X6n
  • 八进制:
0o6n
0O6n
06n  // ❌SyntaxError
  • 二进制:
0b10n
0B10n
  • BigInt 不支持科学计数法形式:
1e25n // ❌ SyntaxError

2. 转换为字符串

当作为 key 时,值会被转换为字符串,而 BigInt 转字符串时是没有后缀 n 的。

String(12n) === "12"

因此:

let obj = { };
obj[32n] = 1;
obj[32] === 1;

数组同理,array[5n] 等同于 array[5] 等同于 array["5"]

:这并不意味着 array[xxxn]array[xxx] 是一样的。因为 BigInt 可以超越 Number 的安全表示范围边界。

let obj = {};
obj[9007199254740993n] = "foo";

obj[9007199254740993n] === "foo"; // ✅
obj["9007199254740993"] === "foo"; // ✅
obj[9007199254740993] === "foo"; // ❌

我们可以通过如下代码查一下原因:

let obj = {};
obj[9007199254740993n] = "foo";
obj[9007199254740993] = "bar";

Object.keys(obj);
// ["9007199254740993", "9007199254740992"]

因为 String(9007199254740993) === "9007199254740992"

3. 零值处理

因为 BigInt 表示的是整数,所以只存在一个 0(无正零和负零之分)。

Object.is(-0, 0) === false
Object.is(-0n, 0n) === true

注意BigInt 中没有 +0n,具体原因见上。

4. 等值判断

BigInt 同值判定规则:

  • 数组:
[0].includes(0n) === false
[0n].includes(0n) === true
[0n].includes(+0) === false
[0n].includes(-0) === false
  • Set
new Set([0]).has(0) === true
new Set([0n]).has(0) === false
new Set([0n]).has(0n) === true
new Set([0]).has(0n) === false
  • Map
new Map([[0n, 42]]).has(0n) === true
new Map([[0n, 42]]).has(0) === false
new Map([[0, 42]]).has(0) === true
new Map([[0, 42]]).has(0n) === false

由于 00n 不相等,所以在集合中,两者可以共存:

let s = new Set([0, 0n]);
s.size === 2;

let m = new Map([[0, 42], [0n, 24]]);
m.size === 2;

5. 与 Number 比较

BigIntNubmer 的不同。

BigInt 只是函数,没有构造器,因此不能使用 new 来创建 BigInt 的实例。

new Number(0); // ✅
new BigInt(0); // ❌

对某些特殊值的处理不同:

  • 当没有参数时,Number 返回 0BigInt 抛出 TypeError
Number() // 0
BigInt() // ❌ TypeError
  • 当非数字时,Number 返回 NaNBigInt 抛出 TypeErrorSyntaxError
Number(undefined) // NaN
BigInt(undefined) // ❌ TypeError

Number(null) // 0
BigInt(null) // ❌ TypeError

Number({}) // NaN
BigInt({}) // ❌ SyntaxError

Number("foo")  // NaN
BigInt("foo") // ❌ SyntaxError
  • 两者对于 -0(负零)的处理也不同
Number(-0) === -0
BigInt(-0) === 0n
  • 两者都会把 true 转换为 1,把 false 转换为 0
Number(true) === 1n
Number(false) === 0n

BigInt(true) === 1n
BigInt(false) === 0n
  • 对于浮点数,BigInt 抛出 RangeError 异常
BigInt(4.00000001) // ❌ RangeError
  • 对于 NaN 和正负无穷,BigInt 抛出 RangeError 异常
BigInt(NaN) // ❌ RangeError
BigInt(-Infinity) // ❌ RangeError
BigInt(+Infinity) // ❌ RangeError

6. 类型转换

BigInt 不能隐式转换为 Number,所以在接受 Number 作为参数的运算中,将抛出 TypeError 异常

isNaN(0n) // ❌TypeError
isFinite(0n) // ❌TypeError
Math.abs(-4n) // ❌TypeError
"bar".substr(1n) // ❌TypeError

但是 Number 下面的函数可以使用:

Number.isSafeInteger(0n) === false
Number.isFinite(0n) === false
Number.isNaN(0n) === false
Number.parseInt(0n) === 0

7. 忠告

我最初看这个提案的适合也觉得不适应,其实本质上都是把 BigInt 作为了 Big Integer(很大的整数)。如果我们把 "bigint" 和 "number" 作为完全不同的数据类型,很多用法就不会太纠结了。

文章被以下专栏收录

    V8 引擎介绍以及源码分析,包括 V8、Node.js、Chrome 等。。。关注公众号 justjavac 回复 V8 可查看历史文章