指数运算符(**)返回第一个操作数取第二个操作数的幂的结果。
x ** y
2 ** 2 // 4
2 ** 3 // 8
指数运算符是右结合的。
a ** b ** c 等于 a ** (b ** c)
2 ** 3 ** 2 // 相当于 2 ** (3 ** 2) 512
指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
let a = 1.5
a **= 2 // 等同于 a = a * a
let b = 4
b **= 3 // 等同于 b = b * b * b;
链判断运算符(可选链运算符)?.
允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。
?.
运算符的功能类似于 .
链式运算符,不同之处在于,在引用为空的情况下不会引起错误,而是返回 undefined。(在链式调用的时候判断,左侧的对象是否为 null 或 undefined,如果是的,就不再往下运算,而是返回 undefined。)
// 错误的写法
const firstName = message.body.user.firstName || 'default'
// && 运算符
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default'
// ?: 三元运算符
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
// .? 可选链运算符
const firstName = message?.body?.user?.firstName || 'default'
const fooValue = myForm.querySelector('input[name=foo]')?.value
链判断运算符 ?. 有三种写法
// obj?.[expr] 用法:没有发现匹配返回 null,发现匹配返回一个数组
let hex = '#C0FFEE'.match(/#([A-Z]+)/i)?.[1]
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于(如果 a?.b() 里面的 a.b 有值,但不是函数,不可调用,那么 a?.b() 是会报错的。)
a == null ? undefined : a.b()
a?.()
// 等同于(如果 a 不是 null 或 undefined ,但也不是函数,那么 a?.() 会报错。)
a == null ? undefined : a()
注意点
(1)短路机制
本质上,?.
运算符相当于一种短路机制,只要不满足条件,就不再往下执行。
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
上面代码中,如果 a 是 undefined 或 null ,那么 x 不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。
(2)括号的影响
如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
(a?.b).c
// 等价于
(a == null ? undefined : a.b).c
上面代码中,?. 对圆括号外部没有影响,不管 a 对象是否存在,圆括号后面的 .c 总是会执行。
一般来说,使用 ?. 运算符的场合,不应该使用圆括号。
(3)报错场合
以下写法是禁止的,会报错。
// 构造函数
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 时,返回其右侧操作数,否则返回左侧操作数。
语法
leftExpr ?? rightExpr
const nullValue = null
const emptyText = '' // 空字符串,是一个假值,Boolean('') === false
const someNumber = 42
const valA = nullValue ?? 'valA 的默认值'
// valA 的默认值
const valB = emptyText ?? 'valB 的默认值'
// ''(空字符串虽然是假值,但不是 null 或者 undefined)
const valC = someNumber ?? 0
// 42
常见用途
(1)为变量赋默认值
读取对象属性的时候,如果某个属性的值是 null 或 undefined,有时候需要为它们指定默认值。常见做法是通过 ||
运算符指定默认值。
逻辑或运算符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 ||
来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,‘’ 或 0 或 false)时。
使用空值合并运算符 ??
可以避免这种情况,只有运算符左侧的值为 null 或 undefined 时,才会返回右侧的值。
let myText = '' // An empty string (which is also a falsy value)
let notFalsyText = myText || 'Hello world'
console.log(notFalsyText) // Hello world
let preservingFalsy = myText ?? 'Hi neighborhood'
console.log(preservingFalsy); // '' (as myText is neither undefined nor null)
(2)与链判断运算符 ?. 配合使用为 null 或 undefined 的值设置默认值。
const animationDuration = response.settings?.animationDuration ?? 300
上面代码中,如果 response.settings 是 null 或 undefined ,或者 response.settings.animationDuration 是 null 或 undefined ,就会返回默认值 300。也就是说,这一行代码包括了两级属性的判断。
注意点
(1)短路机制
与 OR
和 AND
逻辑运算符相似,当左表达式不为 null 或 undefined 时,不会对右表达式进行求值。
function A() {
console.log('函数 A 被调用了')
return undefined
}
function B() {
console.log('函数 B 被调用了')
return false
}
function C() {
console.log('函数 C 被调用了')
return 'foo'
}
console.log(A() ?? C())
// 依次打印 '函数 A 被调用了'、'函数 C 被调用了'、'foo'
// A() 返回了 undefined,所以运算符两边的表达式都被执行了
console.log(B() ?? C())
// 依次打印 '函数 B 被调用了'、'false'
// B() 返回了 false(既不是 null 也不是 undefined)
// 所以右侧表达式没有被执行
(2)优先级问题
??
运算符 和其他逻辑运算符 &&
和 ||
之间的运算优先级/运算顺序是未定义的,组合使用时,必须用括号表明优先级,否则会报错。
null || undefined ?? 'foo' // 抛出 SyntaxError
true || undefined ?? 'foo' // 抛出 SyntaxError
(null || undefined) ?? 'foo' // 返回 'foo'
ES2021 引入了三个新的逻辑赋值运算符(logical assignment operators),将逻辑运算符与赋值运算符进行结合。
逻辑赋值运算符 | 说明 |
---|---|
||= | 逻辑或赋值运算符(x ||= y)运算仅在 x 为假值时为其赋值。等同于 x || (x = y)。 |
&&= | 逻辑与赋值运算符(x &&= y)运算仅在 x 为真值时为其赋值。等同于 x && (x = y)。 |
??= | 逻辑空赋值运算符(x ??= y)仅在 x 是空值(null 或 undefined)时对其赋值。等同于 x ?? (x = y)。 |
这三个运算符 ||=
、&&=
、??=
相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。
用途:为变量或属性设置默认值。
user.id 属性如果不存在,则设为 1,新的写法比老的写法更紧凑一些。
// 老的写法
user.id = user.id || 1
// 新的写法
user.id ||= 1
参数对象 opts 如果不存在属性 foo 和属性 baz,则为这两个属性设置默认值。
function example(opts) {
opts.foo = opts.foo ?? 'bar'
opts.baz ?? (opts.baz = 'qux')
}
function example(opts) {
opts.foo ??= 'bar'
opts.baz ??= 'qux'
}