1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00
es6tutorial/docs/operator.md
2023-04-08 00:17:12 +08:00

352 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 运算符的扩展
本章介绍 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`和属性`baz`则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。
```javascript
function example(opts) {
opts.foo ??= 'bar';
opts.baz ??= 'qux';
}
```
## `#!`命令
Unix 的命令行脚本都支持`#!`命令,又称为 Shebang 或 Hashbang。这个命令放在脚本的第一行用来指定脚本的执行器。
比如 Bash 脚本的第一行。
```bash
#!/bin/sh
```
Python 脚本的第一行。
```python
#!/usr/bin/env python
```
[ES2023](https://github.com/tc39/proposal-hashbang) 为 JavaScript 脚本引入了`#!`命令,写在脚本文件或者模块文件的第一行。
```javascript
// 写在脚本文件第一行
#!/usr/bin/env node
'use strict';
console.log(1);
// 写在模块文件第一行
#!/usr/bin/env node
export {};
console.log(1);
```
有了这一行以后Unix 命令行就可以直接执行脚本。
```bash
# 以前执行脚本的方式
$ node hello.js
# hashbang 的方式
$ ./hello.js
```
对于 JavaScript 引擎来说,会把`#!`理解成注释,忽略掉这一行。