先记住这个答案
两者都能防止「读 undefined 的属性」报错,但停下的条件不一样。?. 只在左边是 null 或 undefined 时短路,返回 undefined;&& 遇到任何假值都会停,并把这个假值原样返回。所以当数据可能是 0、空字符串、false 时,&& 会把合法值当成「不存在」。另外 ?. 一旦短路,后面整条链都不再执行,写法也比 a && a.b && a.b.c 短得多。
?.只在空值时短路&&遇到任何假值都停,并返回这个假值- 一处短路,后面整条链都不再执行
- 调方法用
fn?.(),取下标用arr?.[0]
差别出在假值上
平时对象都存在时,两种写法结果一样。差别只在左边是假值、但又不是 null 或 undefined 的时候才显现,比如 0、''、false。
const user = { name: '', tags: [] }
console.log(JSON.stringify(user.name && user.name.length)) // name 是空串,&& 直接返回 ''
console.log(user.name?.length) // 空串不是 null,照常读 length
const guest = null
console.log(guest && guest.profile.name) // 返回左边的 null
console.log(guest?.profile.name) // 统一返回 undefined
console.log(user.getName?.()) // 方法不存在也不报错查看输出与解释
""
0
null
undefined
undefined第一组最能说明问题。用户名是空字符串,想拿它的长度,正确答案是 0。&& 看到 '' 是假值就停下了,把 '' 返回出去;?. 只关心 '' 是不是 null 或 undefined,不是,就继续读 length。
第二组看返回值:&& 返回的是左边那个值,可能是 null,也可能是 0 或 false,类型不固定;?. 短路后永远是 undefined。后面接 ?? 给默认值时,?. 的结果更好处理。
整条链短路是什么意思
guest?.profile.name 里,只有 guest 后面写了 ?.,.profile 前面的点是普通的点。按直觉,guest?.profile 得到 undefined,再读 .name 应该报错才对,为什么没报?
因为 ?. 短路的是它后面的整条链,一直到这个表达式结束。guest 为空,.profile.name 全都跳过,整体结果直接是 undefined。这也意味着你不需要写成 guest?.profile?.name,除非 profile 本身也可能为空。
- 短路也会跳过函数调用和参数计算
obj?.method(sideEffect())里,obj为空时sideEffect()根本不会执行。a?.[i++]里,a为空时i也不会加 1。这和&&一样,右边不需要算就不算。 但短路只管到这个表达式的边界。(a?.b).c加了括号,括号就是边界,a为空时括号里得到undefined,再读.c就会报错。
容易答错的地方
- 以为
a?.b.c每一层都受保护 ?.只保护紧挨着它的左边那个值。a存在、a.b是undefined时,a?.b.c还是会报TypeError。哪一层可能为空,就在哪一层后面加?.。- 以为
fn?.()能防住「不是函数」 fn?.()只在fn是null或undefined时跳过调用。如果fn是个字符串或数字,照样会报fn is not a function。- 满屏都写
?. - 每个点都加
?.会把本该暴露的 bug 藏起来。比如接口约定data.list一定存在,结果后端漏传了,用?.只会让列表静默显示为空,排查更难。只在业务上确实允许缺失的地方用。
面试官还会怎么问?
?. 能用在赋值左边吗?
不能。obj?.name = 'x' 是语法错误,可选链只能用来读,不能用来写。要写入就先判断对象存在,再赋值。
?. 和 ?? 怎么配合?
?? 是空值合并,同样只在左边是 null 或 undefined 时取右边的值。两者规则一致,常一起用:const size = config?.page?.size ?? 20。如果这里用 ||,size 为 0 时也会被替换成 20。
new 和标签模板能配合可选链吗?
不能。new a?.b() 是语法错误,把可选链用作标签模板的标签,也就是 a?.b 后面直接跟模板字符串,同样是语法错误。另外,delete a?.b 是允许的,a 为空时什么都不做,返回 true。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。