ES6 学习笔记(下下) | 青训营笔记

69 阅读7分钟

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 所能表示的最大值。如果对这个值加1nBigInt.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 新增了两个类型BigUint64ArrayBigInt64Array,这两种数据类型返回的都是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()的参数是一个对象时,变量xy才会通过解构赋值生成。如果函数foo()调用时没提供参数,变量xy就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。

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()的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。