BigInt函数
JS 原生提供BigInt函数,可以将括号内的参数转换成 BigInt 类型的变量,函数规则和 Number 函数类似。
需要注意的是 BigInt 函数必须要有参数,且参数必须可以正常转换成数值,否则就会报错。
new BigInt() // TypeError
BigInt(undefined) //TypeError
BigInt(null) // TypeError
BigInt('123n') // SyntaxError
BigInt('abc') // SyntaxError
其中要注意的是,如果是使用Number函数,那么'123n'是无法转换成 Number 类型的。
BigInt函数如果参数是小数依然会报错。
BigInt(1.5) // RangeError
BigInt('1.5') // SyntaxError
BigInt继承了Object对象的两个方法。
BigInt.prototype.toString()BigInt.prototype.valueOf()
它还继承了 Number 对象的一个实例方法。
BigInt.prototype.toLocaleString()
此外,还提供了三个静态方法。
BigInt.asUintN(width, BigInt): 给定的 BigInt 转为 0 到 2^width - 1 之间对应的值。BigInt.asIntN(width, BigInt):给定的 BigInt 转为 -2^width - 1 到 2^width - 1 - 1 之间对应的值。BigInt.parseInt(string[, radix]):近似于Number.parseInt(),将一个字符串转换成指定进制的 BigInt。
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max)
// 9223372036854775807n
BigInt.asIntN(64, max + 1n)
// -9223372036854775808n
BigInt.asUintN(64, max + 1n)
// 9223372036854775808n
上面代码中,max是64位带符号的 BigInt 所能表示的最大值。如果对这个值加1n,BigInt.asIntN()将会返回一个负值,因为这时新增的一位将被解释为符号位。而BigInt.asUintN()方法由于不存在符号位,所以可以正确返回结果。
如果BigInt.asIntN()和BigInt.asUintN()指定的位数,小于数值本身的位数,那么头部的位将被舍弃。
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(32, max) // -1n
BigInt.asUintN(32, max) // 4294967295n
上面代码中,max是一个64位的 BigInt,如果转为32位,前面的32位都会被舍弃。
下面是BigInt.parseInt()的例子。
// Number.parseInt() 与 BigInt.parseInt() 的对比
Number.parseInt('9007199254740993', 10)
// 9007199254740992
BigInt.parseInt('9007199254740993', 10)
// 9007199254740993n
上面代码中,由于有效数字超出了最大限度,Number.parseInt方法返回的结果是不精确的,而BigInt.parseInt方法正确返回了对应的 BigInt。
对于二进制数组,BigInt 新增了两个类型BigUint64Array和BigInt64Array,这两种数据类型返回的都是64位 BigInt。DataView对象的实例方法DataView.prototype.getBigInt64()和DataView.prototype.getBigUint64(),返回的也是 BigInt。
转换规则
可以使用Boolean()、Number()和String()这三个方法,将 BigInt 可以转为布尔值、数值和字符串类型。
Boolean(0n) // false
Boolean(1n) // true
Number(1n) // 1
String(1n) // "1"
注意在使用String()方法的时候,最后的 n 会消失。
另外,取反运算符(!)也可以将 BigInt 转为布尔值。
!0n // true
!1n // false
数学运算
+、-、*、**这些基本的二元运算符与 Number 类型行为一致,除法``会舍去小数部分,返回整数。
几乎所有的数值运算符都可以用在 BigInt 上,除了:
- 不带符号的右移位运算符
- 一元的求正运算符
前者无法使用是因为右移位运算符是不带符号的,而 BigInt 总是带符号的,导致该运算无意义。后者是因为一元运算符+在 asm.js 里面总是返回 Number 类型,为了不破坏 asm.js 就规定+1n会报错。
BigInt 不能与普通数值进行混合运算。
1n + 1.3 // 报错
上面代码报错是因为无论返回的是 BigInt 或 Number,都会导致丢失精度信息。比如(2n**53n + 1n) + 0.5这个表达式,如果返回 BigInt 类型,0.5这个小数部分会丢失;如果返回 Number 类型,有效精度只能保持 53 位,导致精度下降。
同样的原因,如果一个标准库函数的参数预期是 Number 类型,但是得到的是一个 BigInt,就会报错。
// 错误的写法
Math.sqrt(4n) // 报错
// 正确的写法
Math.sqrt(Number(4n)) // 2
上面代码中,Math.sqrt的参数预期是 Number 类型,如果是 BigInt 就会报错,必须先用Number方法转一下类型,才能进行计算。
asm.js 里面,|0跟在一个数值的后面会返回一个32位整数。根据不能与 Number 类型混合运算的规则,BigInt 如果与|0进行运算会报错。
1n | 0 // 报错
其他运算
BigInt 对应的布尔值,与 Number 类型一致,即0n会转为false,其他值转为true。
if (0n) {
console.log('if');
} else {
console.log('else');
}
// else
上面代码中,0n对应false,所以会进入else子句。
比较运算符(比如>)和相等运算符(==)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。
0n < 1 // true
0n < true // true
0n == 0 // true
0n == false // true
0n === 0 // false
BigInt 与字符串混合运算时,会先转为字符串,再进行运算。
'' + 123n // "123"
函数的扩展
函数参数的默认值
基本用法
在 ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法,如下。
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World
上面的代码中,log函数的第一行会先检查参数y有没有被赋值,如果有的话就会将指定值 World 赋值给y,但是这种写法的缺点在于,如果y被赋值了,但是被赋值之后他的 boolean 值为 false,那么对应的赋值就不生效,y还是会被设定为默认值 World,就比如上面的代码中y一开始被赋值成了空字符,那么他的 boolean 值就是false。
因此为了避免上面的问题,需要先判断一下参数y是否被赋值,如果没有,再等同于默认值。
if (typeof y === 'undefined') {
y = 'World';
}
而在 ES6 之后,函数的参数允许设置默认值,具体如下。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
函数参数的变量是默认声明的,所以不能再声明一次,否则会报错。
使用参数默认值的时候,函数不能有同名参数。
// 不报错
function foo(x, x, y) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
参数默认值是不传值的,而是每次都重新计算默认值表达式的值,也就是说,函数参数默认值是惰性求值的。
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
上面代码中,参数p的默认值是x + 1。这时,每次调用函数foo(),都会重新计算x + 1,而不是默认p等于 100。
与解构赋值默认值结合使用
参数默认值可以与解构赋值默认值结合使用。
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo()的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo()调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
上面代码指定,如果没有提供参数,函数foo的参数默认为一个空对象。
下面是另一个解构赋值默认值的例子。
function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
// 报错
上面代码中,如果函数fetch()的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。