在实际应用中,如果读取对象内部 的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName这个属性,安全的写法是写成下下面这样:
// 错误的写法
const firstName = message.body.user.firstName || 'default'
// 正确的写法
const firstName = (message && message.body && message.body.user && messaage.body.user.firstName) || 'default'
上面的例子,firstName属性在对象的第四层,所以需要判断四次,每一层是否有值。三元运算符也常用于判断对象是否存在
const fooInput = myForm.querySelector('input[name=foo]')
const footVal = fooInput ?footInput.value : undefined
上面的例子,必须先判断fooInput是否存在,才能读取fooInput.value
这样层层判断非常麻烦,因此ES2020引入了“链式判断运算符”---?.,简化上面的写法
const firstName = message?.body?.user?.firstName || 'default'
const fooVal = myForm.querySelector('input[name=foo]')?.value
上面代码中使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或者undefined,如果是的,就不再往下运算,而是返回undefined
下面是判断 对象方法是否存在,如果存在就立即执行的例子
iterator.return?.()
上面的代码中,iterator.return如果有定义,就会调用该方法,否则iterator.return直接返回undefined,不再执行?.后面的部分
对于那些可能没有实现的方法,这个运算符尤其有用
if(myForm.checkValidity?.()===false){
return
}
上面代码中,老式浏览器的表单对象可能没有checkValidity()这个方法,这时?.运算符就会返回undefined,判断语句就变成了undefined===false,所以就会跳过下面的代码
链式判断运算符?.有是那种写法
下面是obj?.[expr]用法的一个例子
let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1];
上面代码中,字符串match()方法,如果没有发现匹配会返回null,如果发现匹配会返回一个数组,?.运算符起到了判断作用
下面?.运算符常见形式,以及不实用运算符时的等价形式
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)短路机制
本质上,?.运算符相当于一个短路机制,只要不满足条件,就不会往下执行
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