mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 10:22:23 +00:00
docs: add ES2021
This commit is contained in:
parent
8e236cb8b2
commit
fdac6ccb2e
124
docs/number.md
124
docs/number.md
@ -31,6 +31,97 @@ Number('0b111') // 7
|
||||
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()
|
||||
|
||||
ES6 在`Number`对象上,新提供了`Number.isFinite()`和`Number.isNaN()`两个方法。
|
||||
@ -655,42 +746,11 @@ ES6 新增了 6 个双曲函数方法。
|
||||
- `Math.acosh(x)` 返回`x`的反双曲余弦(inverse hyperbolic cosine)
|
||||
- `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 数据类型
|
||||
|
||||
### 简介
|
||||
|
||||
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。
|
||||
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。
|
||||
|
||||
```javascript
|
||||
// 超过 53 个二进制位的数值,无法保持精度
|
||||
|
229
docs/object.md
229
docs/object.md
@ -704,232 +704,3 @@ let aWithXGetter = { ...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`不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。
|
||||
|
||||
(2)delete 运算符
|
||||
|
||||
```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
309
docs/operator.md
Normal 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';
|
||||
}
|
||||
```
|
||||
|
@ -758,9 +758,25 @@ try {
|
||||
|
||||
## 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
|
||||
const promises = [
|
||||
@ -768,6 +784,7 @@ const promises = [
|
||||
fetch('/endpoint-b').then(() => 'b'),
|
||||
fetch('/endpoint-c').then(() => 'c'),
|
||||
];
|
||||
|
||||
try {
|
||||
const first = await Promise.any(promises);
|
||||
console.log(first);
|
||||
@ -778,30 +795,18 @@ try {
|
||||
|
||||
上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled`,`Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么`await`命令就会抛出错误。
|
||||
|
||||
`Promise.any()`抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。
|
||||
`Promise.any()`抛出的错误,不是一个一般的 Error 错误对象,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。
|
||||
|
||||
```javascript
|
||||
new AggregateError() extends Array -> AggregateError
|
||||
// new AggregateError() extends Array
|
||||
|
||||
const err = new AggregateError();
|
||||
err.push(new Error("first error"));
|
||||
err.push(new Error("second error"));
|
||||
// ...
|
||||
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
|
||||
|
@ -314,83 +314,6 @@ const userAge = userId |> await fetchUserById |> getAgeFromUser;
|
||||
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.sign()`用来判断一个值的正负,但是如果参数是`-0`,它会返回`-0`。
|
||||
|
148
docs/set-map.md
148
docs/set-map.md
@ -1140,3 +1140,151 @@ c.dec()
|
||||
|
||||
上面代码中,`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()`,输出一条错误信息。
|
||||
|
||||
由于无法知道清理器何时会执行,所以最好避免使用它。另外,如果浏览器窗口关闭或者进程意外退出,清理器则不会运行。
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user