1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00

docs: add ES2021

This commit is contained in:
ruanyf 2021-07-10 14:58:15 +08:00
parent 8e236cb8b2
commit fdac6ccb2e
6 changed files with 571 additions and 355 deletions

View File

@ -31,6 +31,97 @@ Number('0b111') // 7
Number('0o10') // 8 Number('0o10') // 8
``` ```
## 数值分隔符
欧美语言中,较长的数值允许每三位添加一个分隔符(通常是一个逗号),增加数值的可读性。比如,`1000`可以写作`1,000`
[ES2021](https://github.com/tc39/proposal-numeric-separator),允许 JavaScript 的数值使用下划线(`_`)作为分隔符。
```javascript
let budget = 1_000_000_000_000;
budget === 10 ** 12 // true
```
这个数值分隔符没有指定间隔的位数,也就是说,可以每三位添加一个分隔符,也可以每一位、每两位、每四位添加一个。
```javascript
123_00 === 12_300 // true
12345_00 === 123_4500 // true
12345_00 === 1_234_500 // true
```
小数和科学计数法也可以使用数值分隔符。
```javascript
// 小数
0.000_001
// 科学计数法
1e10_000
```
数值分隔符有几个使用注意点。
- 不能放在数值的最前面leading或最后面trailing
- 不能两个或两个以上的分隔符连在一起。
- 小数点的前后不能有分隔符。
- 科学计数法里面,表示指数的`e``E`前后不能有分隔符。
下面的写法都会报错。
```javascript
// 全部报错
3_.141
3._141
1_e12
1e_12
123__456
_1464301
1464301_
```
除了十进制,其他进制的数值也可以使用分隔符。
```javascript
// 二进制
0b1010_0001_1000_0101
// 十六进制
0xA0_B0_C0
```
可以看到,数值分隔符可以按字节顺序分隔数值,这在操作二进制位时,非常有用。
注意,分隔符不能紧跟着进制的前缀`0b``0B``0o``0O``0x``0X`
```javascript
// 报错
0_b111111000
0b_111111000
```
数值分隔符只是一种书写便利,对于 JavaScript 内部数值的存储和输出,并没有影响。
```javascript
let num = 12_345;
num // 12345
num.toString() // 12345
```
上面示例中,变量`num`的值为`12_345`,但是内部存储和输出的时候,都不会有数值分隔符。
下面三个将字符串转成数值的函数,不支持数值分隔符。主要原因是语言的设计者认为,数值分隔符主要是为了编码时书写数值的方便,而不是为了处理外部输入的数据。
- Number()
- parseInt()
- parseFloat()
```javascript
Number('123_456') // NaN
parseInt('123_456') // 123
```
## Number.isFinite(), Number.isNaN() ## Number.isFinite(), Number.isNaN()
ES6 在`Number`对象上,新提供了`Number.isFinite()``Number.isNaN()`两个方法。 ES6 在`Number`对象上,新提供了`Number.isFinite()``Number.isNaN()`两个方法。
@ -655,42 +746,11 @@ ES6 新增了 6 个双曲函数方法。
- `Math.acosh(x)` 返回`x`的反双曲余弦inverse hyperbolic cosine - `Math.acosh(x)` 返回`x`的反双曲余弦inverse hyperbolic cosine
- `Math.atanh(x)` 返回`x`的反双曲正切inverse hyperbolic tangent - `Math.atanh(x)` 返回`x`的反双曲正切inverse hyperbolic tangent
## 指数运算符
ES2016 新增了一个指数运算符(`**`)。
```javascript
2 ** 2 // 4
2 ** 3 // 8
```
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
```javascript
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
```
上面代码中,首先计算的是第二个指数运算符,而不是第一个。
指数运算符可以与等号结合,形成一个新的赋值运算符(`**=`)。
```javascript
let a = 1.5;
a **= 2;
// 等同于 a = a * a;
let b = 4;
b **= 3;
// 等同于 b = b * b * b;
```
## BigInt 数据类型 ## BigInt 数据类型
### 简介 ### 简介
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位大于这个范围的整数JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值JavaScript 无法表示,会返回`Infinity` JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位大于这个范围的整数JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值JavaScript 无法表示,会返回`Infinity`
```javascript ```javascript
// 超过 53 个二进制位的数值,无法保持精度 // 超过 53 个二进制位的数值,无法保持精度

View File

@ -704,232 +704,3 @@ let aWithXGetter = { ...a }; // 报错
上面例子中,取值函数`get`在扩展`a`对象时会自动执行,导致报错。 上面例子中,取值函数`get`在扩展`a`对象时会自动执行,导致报错。
## 链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。
```javascript
// 错误的写法
const firstName = message.body.user.firstName;
// 正确的写法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
```
上面例子中,`firstName`属性在对象的第四层,所以需要判断四次,每一层是否有值。
三元运算符`?:`也常用于判断对象是否存在。
```javascript
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
```
上面例子中,必须先判断`fooInput`是否存在,才能读取`fooInput.value`
这样的层层判断非常麻烦,因此 [ES2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”optional chaining operator`?.`,简化上面的写法。
```javascript
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
```
上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null``undefined`。如果是的,就不再往下运算,而是返回`undefined`
下面是判断对象方法是否存在,如果存在就立即执行的例子。
```javascript
iterator.return?.()
```
上面代码中,`iterator.return`如果有定义,就会调用该方法,否则`iterator.return`直接返回`undefined`,不再执行`?.`后面的部分。
对于那些可能没有实现的方法,这个运算符尤其有用。
```javascript
if (myForm.checkValidity?.() === false) {
// 表单校验失败
return;
}
```
上面代码中,老式浏览器的表单可能没有`checkValidity`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。
链判断运算符有三种用法。
- `obj?.prop` // 对象属性
- `obj?.[expr]` // 同上
- `func?.(...args)` // 函数或对象方法的调用
下面是`obj?.[expr]`用法的一个例子。
```bash
let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1];
```
上面例子中,字符串的`match()`方法,如果没有发现匹配会返回`null`,如果发现匹配会返回一个数组,`?.`运算符起到了判断作用。
下面是`?.`运算符常见形式,以及不使用该运算符时的等价形式。
```javascript
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
```
上面代码中,特别注意后两种形式,如果`a?.b()`里面的`a.b`不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null``undefined`,但也不是函数,那么`a?.()`会报错。
使用这个运算符,有几个注意点。
1短路机制
`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。
```javascript
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
```
上面代码中,如果`a``undefined``null`,那么`x`不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。
2delete 运算符
```javascript
delete a?.b
// 等同于
a == null ? undefined : delete a.b
```
上面代码中,如果`a``undefined``null`,会直接返回`undefined`,而不会进行`delete`运算。
3括号的影响
如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
```javascript
(a?.b).c
// 等价于
(a == null ? undefined : a.b).c
```
上面代码中,`?.`对圆括号外部没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。
一般来说,使用`?.`运算符的场合,不应该使用圆括号。
4报错场合
以下写法是禁止的,会报错。
```javascript
// 构造函数
new a?.()
new a?.b()
// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`
// 链判断运算符的左侧是 super
super?.()
super?.foo
// 链运算符用于赋值运算符左侧
a?.b = c
```
5右侧不得为十进制数值
为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
## Null 判断运算符
读取对象属性的时候,如果某个属性的值是`null``undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。
```javascript
const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;
```
上面的三行代码都通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null``undefined`,默认值就会生效,但是属性的值如果为空字符串或`false``0`,默认值也会生效。
为了避免这种情况,[ES2020](https://github.com/tc39/proposal-nullish-coalescing) 引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null``undefined`时,才会返回右侧的值。
```javascript
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
```
上面代码中,默认值只有在左侧属性值为`null``undefined`时,才会生效。
这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null``undefined`的值设置默认值。
```javascript
const animationDuration = response.settings?.animationDuration ?? 300;
```
上面代码中,如果`response.settings``null``undefined`,或者`response.settings.animationDuration``null``undefined`就会返回默认值300。也就是说这一行代码包括了两级属性的判断。
这个运算符很适合判断函数参数是否赋值。
```javascript
function Component(props) {
const enable = props.enabled ?? true;
// …
}
```
上面代码判断`props`参数的`enabled`属性是否赋值,基本等同于下面的写法。
```javascript
function Component(props) {
const {
enabled: enable = true,
} = props;
// …
}
```
`??`有一个运算优先级问题,它与`&&``||`的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。
```javascript
// 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
```
上面四个表达式都会报错,必须加入表明优先级的括号。
```javascript
(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);
(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);
(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);
(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
```

309
docs/operator.md Normal file
View File

@ -0,0 +1,309 @@
# 运算符的扩展
本章介绍 ES6 后续标准添加的一些运算符。
## 指数运算符
ES2016 新增了一个指数运算符(`**`)。
```javascript
2 ** 2 // 4
2 ** 3 // 8
```
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
```javascript
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
```
上面代码中,首先计算的是第二个指数运算符,而不是第一个。
指数运算符可以与等号结合,形成一个新的赋值运算符(`**=`)。
```javascript
let a = 1.5;
a **= 2;
// 等同于 a = a * a;
let b = 4;
b **= 3;
// 等同于 b = b * b * b;
```
## 链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取`message.body.user.firstName`这个属性,安全的写法是写成下面这样。
```javascript
// 错误的写法
const firstName = message.body.user.firstName || 'default';
// 正确的写法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
```
上面例子中,`firstName`属性在对象的第四层,所以需要判断四次,每一层是否有值。
三元运算符`?:`也常用于判断对象是否存在。
```javascript
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
```
上面例子中,必须先判断`fooInput`是否存在,才能读取`fooInput.value`
这样的层层判断非常麻烦,因此 [ES2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”optional chaining operator`?.`,简化上面的写法。
```javascript
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
```
上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null``undefined`。如果是的,就不再往下运算,而是返回`undefined`
下面是判断对象方法是否存在,如果存在就立即执行的例子。
```javascript
iterator.return?.()
```
上面代码中,`iterator.return`如果有定义,就会调用该方法,否则`iterator.return`直接返回`undefined`,不再执行`?.`后面的部分。
对于那些可能没有实现的方法,这个运算符尤其有用。
```javascript
if (myForm.checkValidity?.() === false) {
// 表单校验失败
return;
}
```
上面代码中,老式浏览器的表单对象可能没有`checkValidity()`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。
链判断运算符`?.`有三种写法。
- `obj?.prop` // 对象属性是否存在
- `obj?.[expr]` // 同上
- `func?.(...args)` // 函数或对象方法是否存在
下面是`obj?.[expr]`用法的一个例子。
```bash
let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1];
```
上面例子中,字符串的`match()`方法,如果没有发现匹配会返回`null`,如果发现匹配会返回一个数组,`?.`运算符起到了判断作用。
下面是`?.`运算符常见形式,以及不使用该运算符时的等价形式。
```javascript
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
```
上面代码中,特别注意后两种形式,如果`a?.b()``a?.()`。如果`a?.b()`里面的`a.b`有值,但不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null``undefined`,但也不是函数,那么`a?.()`会报错。
使用这个运算符,有几个注意点。
1短路机制
本质上,`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。
```javascript
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
```
上面代码中,如果`a``undefined``null`,那么`x`不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。
2括号的影响
如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
```javascript
(a?.b).c
// 等价于
(a == null ? undefined : a.b).c
```
上面代码中,`?.`对圆括号外部没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。
一般来说,使用`?.`运算符的场合,不应该使用圆括号。
3报错场合
以下写法是禁止的,会报错。
```javascript
// 构造函数
new a?.()
new a?.b()
// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`
// 链判断运算符的左侧是 super
super?.()
super?.foo
// 链运算符用于赋值运算符左侧
a?.b = c
```
4右侧不得为十进制数值
为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
## Null 判断运算符
读取对象属性的时候,如果某个属性的值是`null``undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。
```javascript
const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;
```
上面的三行代码都通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null``undefined`,默认值就会生效,但是属性的值如果为空字符串或`false``0`,默认值也会生效。
为了避免这种情况,[ES2020](https://github.com/tc39/proposal-nullish-coalescing) 引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null``undefined`时,才会返回右侧的值。
```javascript
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
```
上面代码中,默认值只有在左侧属性值为`null``undefined`时,才会生效。
这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null``undefined`的值设置默认值。
```javascript
const animationDuration = response.settings?.animationDuration ?? 300;
```
上面代码中,如果`response.settings``null``undefined`,或者`response.settings.animationDuration``null``undefined`就会返回默认值300。也就是说这一行代码包括了两级属性的判断。
这个运算符很适合判断函数参数是否赋值。
```javascript
function Component(props) {
const enable = props.enabled ?? true;
// …
}
```
上面代码判断`props`参数的`enabled`属性是否赋值,基本等同于下面的写法。
```javascript
function Component(props) {
const {
enabled: enable = true,
} = props;
// …
}
```
`??`本质上是逻辑运算,它与其他两个逻辑运算符`&&``||`有一个优先级问题,它们之间的优先级到底孰高孰低。优先级的不同,往往会导致逻辑运算的结果不同。
现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。
```javascript
// 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
```
上面四个表达式都会报错,必须加入表明优先级的括号。
```javascript
(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);
(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);
(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);
(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
```
## 逻辑赋值运算符
ES2021 引入了三个新的[逻辑赋值运算符](https://github.com/tc39/proposal-logical-assignment)logical assignment operators将逻辑运算符与赋值运算符进行结合。
```javascript
// 或赋值运算符
x ||= y
// 等同于
x || (x = y)
// 与赋值运算符
x &&= y
// 等同于
x && (x = y)
// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)
```
这三个运算符`||=``&&=``??=`相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。
它们的一个用途是,为变量或属性设置默认值。
```javascript
// 老的写法
user.id = user.id || 1;
// 新的写法
user.id ||= 1;
```
上面示例中,`user.id`属性如果不存在,则设为`1`,新的写法比老的写法更紧凑一些。
下面是另一个例子。
```javascript
function example(opts) {
opts.foo = opts.foo ?? 'bar';
opts.baz ?? (opts.baz = 'qux');
}
```
上面示例中,参数对象`opts`如果不存在属性`foo`和属性`bar`则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。
```javascript
function example(opts) {
opts.foo ??= 'bar';
opts.baz ??= 'qux';
}
```

View File

@ -758,9 +758,25 @@ try {
## Promise.any() ## Promise.any()
ES2021 引入了[`Promise.any()`方法](https://github.com/tc39/proposal-promise-any)。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。 ES2021 引入了[`Promise.any()`方法](https://github.com/tc39/proposal-promise-any)。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
`Promise.any()``Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。 ```javascript
Promise.any([
fetch('https://v8.dev/').then(() => 'home'),
fetch('https://v8.dev/blog').then(() => 'blog'),
fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => { // 只要有一个 fetch() 请求成功
console.log(first);
}).catch((error) => { // 所有三个 fetch() 全部请求失败
console.log(error);
});
```
只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。
`Promise.any()``Promise.race()`方法很像,只有一点不同,就是`Promise.any()`不会因为某个 Promise 变成`rejected`状态而结束,必须等到所有参数 Promise 变成`rejected`状态才会结束。
下面是`Promise()``await`命令结合使用的例子。
```javascript ```javascript
const promises = [ const promises = [
@ -768,6 +784,7 @@ const promises = [
fetch('/endpoint-b').then(() => 'b'), fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'), fetch('/endpoint-c').then(() => 'c'),
]; ];
try { try {
const first = await Promise.any(promises); const first = await Promise.any(promises);
console.log(first); console.log(first);
@ -778,30 +795,18 @@ try {
上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled``Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么`await`命令就会抛出错误。 上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled``Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么`await`命令就会抛出错误。
`Promise.any()`抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。 `Promise.any()`抛出的错误,不是一个一般的 Error 错误对象,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。
```javascript ```javascript
new AggregateError() extends Array -> AggregateError // new AggregateError() extends Array
const err = new AggregateError(); const err = new AggregateError();
err.push(new Error("first error")); err.push(new Error("first error"));
err.push(new Error("second error")); err.push(new Error("second error"));
// ...
throw err; throw err;
``` ```
捕捉错误时,如果不用`try...catch`结构和 await 命令,可以像下面这样写。
```javascript
Promise.any(promises).then(
(first) => {
// Any of the promises was fulfilled.
},
(error) => {
// All of the promises were rejected.
}
);
```
下面是一个例子。 下面是一个例子。
```javascript ```javascript

View File

@ -314,83 +314,6 @@ const userAge = userId |> await fetchUserById |> getAgeFromUser;
const userAge = getAgeFromUser(await fetchUserById(userId)); const userAge = getAgeFromUser(await fetchUserById(userId));
``` ```
## 数值分隔符
欧美语言中,较长的数值允许每三位添加一个分隔符(通常是一个逗号),增加数值的可读性。比如,`1000`可以写作`1,000`
现在有一个[提案](https://github.com/tc39/proposal-numeric-separator),允许 JavaScript 的数值使用下划线(`_`)作为分隔符。
```javascript
let budget = 1_000_000_000_000;
budget === 10 ** 12 // true
```
JavaScript 的数值分隔符没有指定间隔的位数,也就是说,可以每三位添加一个分隔符,也可以每一位、每两位、每四位添加一个。
```javascript
123_00 === 12_300 // true
12345_00 === 123_4500 // true
12345_00 === 1_234_500 // true
```
小数和科学计数法也可以使用数值分隔符。
```javascript
// 小数
0.000_001
// 科学计数法
1e10_000
```
数值分隔符有几个使用注意点。
- 不能在数值的最前面leading或最后面trailing
- 不能两个或两个以上的分隔符连在一起。
- 小数点的前后不能有分隔符。
- 科学计数法里面,表示指数的`e``E`前后不能有分隔符。
下面的写法都会报错。
```javascript
// 全部报错
3_.141
3._141
1_e12
1e_12
123__456
_1464301
1464301_
```
除了十进制,其他进制的数值也可以使用分隔符。
```javascript
// 二进制
0b1010_0001_1000_0101
// 十六进制
0xA0_B0_C0
```
注意,分隔符不能紧跟着进制的前缀`0b``0B``0o``0O``0x``0X`
```javascript
// 报错
0_b111111000
0b_111111000
```
下面三个将字符串转成数值的函数,不支持数值分隔符。主要原因是提案的设计者认为,数值分隔符主要是为了编码时书写数值的方便,而不是为了处理外部输入的数据。
- Number()
- parseInt()
- parseFloat()
```javascript
Number('123_456') // NaN
parseInt('123_456') // 123
```
## Math.signbit() ## Math.signbit()
`Math.sign()`用来判断一个值的正负,但是如果参数是`-0`,它会返回`-0` `Math.sign()`用来判断一个值的正负,但是如果参数是`-0`,它会返回`-0`

View File

@ -1140,3 +1140,151 @@ c.dec()
上面代码中,`Countdown`类的两个内部属性`_counter``_action`,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 上面代码中,`Countdown`类的两个内部属性`_counter``_action`,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。
## WeakRef
WeakSet 和 WeakMap 是基于弱引用的数据结构,[ES2021](https://github.com/tc39/proposal-weakrefs) 更进一步,提供了 WeakRef 对象,用于直接创建对象的弱引用。
```javascript
let target = {};
let wr = new WeakRef(target);
```
上面示例中,`target`是原始对象,构造函数`WeakRef()`创建了一个基于`target`的新对象`wr`。这里,`wr`就是一个 WeakRef 的示例,属于对`target`的弱引用,垃圾回收机制不会计入这个引用,也就是说,`wr`的引入不会妨碍原始对象`target`被垃圾回收机制清除。
WeakRef 实例对象有一个`deref()`方法,如果原始对象存在,该方法返回原始对象;如果原始对象已经被垃圾回收机制清除,该方法返回`undefined`
```javascript
let target = {};
let wr = new WeakRef(target);
let obj = wr.deref();
if (obj) { // target 未被垃圾回收机制清除
// ...
}
```
上面示例中,`deref()`方法可以判断原始对象是否已被清除。
弱引用对象的一大用处,就是作为缓存,未被清除时可以从缓存取值,一旦清除缓存就自动失效。
```javascript
function makeWeakCached(f) {
const cache = new Map();
return key => {
const ref = cache.get(key);
if (ref) {
const cached = ref.deref();
if (cached !== undefined) return cached;
}
const fresh = f(key);
cache.set(key, new WeakRef(fresh));
return fresh;
};
}
const getImageCached = makeWeakCached(getImage);
```
上面示例中,`makeWeakCached()`用于建立一个缓存,缓存里面保存对原始文件的弱引用。
注意,标准规定,一旦使用`WeakRef()`创建了原始对象的弱引用那么在本轮事件循环event loop原始对象肯定不会被清除只会在后面的事件循环才会被清除。
## FinalizationRegistry
[ES2021](https://github.com/tc39/proposal-weakrefs#finalizers) 引入了清理器注册表功能 FinalizationRegistry用来指定目标对象被垃圾回收机制清除以后所要执行的回调函数。
首先,新建一个注册表实例。
```javascript
const registry = new FinalizationRegistry(heldValue => {
// ....
});
```
上面代码中,`FinalizationRegistry()`是系统提供的构造函数,返回一个清理器注册表实例,里面登记了所要执行的回调函数。回调函数作为`FinalizationRegistry()`的参数传入,它本身有一个参数`heldValue`
然后,注册表实例的`register()`方法,用来注册所要观察的目标对象。
```javascript
registry.register(theObject, "some value");
```
上面示例中,`theObject`就是所要观察的目标对象,一旦该对象被垃圾回收机制清除,注册表就会在清除完成后,调用早前注册的回调函数,并将`some value`作为参数(前面的`heldValue`)传入回调函数。
注意,注册表不对目标对象`theObject`构成强引用,属于弱引用。因为强引用的话,原始对象就不会被垃圾回收机制清除,这就失去使用注册表的意义了。
回调函数的参数`heldValue`可以是任意类型的值,字符串、数值、布尔值、对象,甚至可以是`undefined`
最后,如果以后还想取消已经注册的回调函数,则要向`register()`传入第三个参数,作为标记值。这个标记值必须是对象,一般都用原始对象。接着,再使用注册表实例对象的`unregister()`方法取消注册。
```javascript
registry.register(theObject, "some value", theObject);
// ...其他操作...
registry.unregister(theObject);
```
上面代码中,`register()`方法的第三个参数就是标记值`theObject`。取消回调函数时,要使用`unregister()`方法,并将标记值作为该方法的参数。这里`register()`方法对第三个参数的引用,也属于弱引用。如果没有这个参数,则回调函数无法取消。
由于回调函数被调用以后,就不再存在于注册表之中了,所以执行`unregister()`应该是在回调函数还没被调用之前。
下面使用`FinalizationRegistry`,对前一节的缓存函数进行增强。
```javascript
function makeWeakCached(f) {
const cache = new Map();
const cleanup = new FinalizationRegistry(key => {
const ref = cache.get(key);
if (ref && !ref.deref()) cache.delete(key);
});
return key => {
const ref = cache.get(key);
if (ref) {
const cached = ref.deref();
if (cached !== undefined) return cached;
}
const fresh = f(key);
cache.set(key, new WeakRef(fresh));
cleanup.register(fresh, key);
return fresh;
};
}
const getImageCached = makeWeakCached(getImage);
```
上面示例与前一节的例子相比,就是增加一个清理器注册表,一旦缓存的原始对象被垃圾回收机制清除,会自动执行一个回调函数。该回调函数会清除缓存里面已经失效的键。
下面是另一个例子。
```javascript
class Thingy {
#file;
#cleanup = file => {
console.error(
`The \`release\` method was never called for the \`Thingy\` for the file "${file.name}"`
);
};
#registry = new FinalizationRegistry(this.#cleanup);
constructor(filename) {
this.#file = File.open(filename);
this.#registry.register(this, this.#file, this.#file);
}
release() {
if (this.#file) {
this.#registry.unregister(this.#file);
File.close(this.#file);
this.#file = null;
}
}
}
```
上面示例中,如果由于某种原因,`Thingy`类的实例对象没有调用`release()`方法,就被垃圾回收机制清除了,那么清理器就会调用回调函数`#cleanup()`,输出一条错误信息。
由于无法知道清理器何时会执行,所以最好避免使用它。另外,如果浏览器窗口关闭或者进程意外退出,清理器则不会运行。