前言
说起来,可选链操作符( optional chaining
)都加入 babel
套餐很久了,笔者还是没有在实践中大量使用,第一个是对这个特性还是半吊子的理解,第二个是没养成这个习惯~
这里列举一下常用的场景,以供下次能在项目实践中更好的使用。
可选链操作符介绍
由于 JavaScript 是一门动态语言,所以我们访问对象属性的时候总是会很小心的做一系列的防御性编程。
比如我们要访问对象的某个属性,但是对象是后端接口返回的,有可能为 null
,这时候我们在访问属性的时候一般会这样写 obj.key
,但是如果 obj
为 null
,那么这时候你就会得到浏览器给你的一个错误提示,如下 😂。

在日常编码中这种操作其实也很常见,我们一般这样编码避免产生这种错误
obj && obj.name
。但这样属性是一层的还好,但某些极端的场景下,你的数据层级是很深的,比如
obj.a.b.c.d.name
。
没有可选链之前,我们会使用一些第三方的库函数去解决这个问题。
- lodash.get
- 使用 es6 解构(也需要一层层的剥开去写,深层解构依然烦琐)
- 自己实现 safeGet, 或者学习 you-dont-need-lodash 里面的 get 方法 -> 代码出处
const get = (obj, path, defaultValue = undefined) => {
const travel = regexp =>
String.prototype.split
.call(path, regexp)
.filter(Boolean)
.reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj);
const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/);
return result === undefined || result === obj ? defaultValue : result;
};
有了可选链之后呢?
先来看一个栗子🌰.
const obj = { a: { b: [{ name: 'obj' }] } }
// 原本的写法
console.log(obj && obj.a && obj.a.b.length && obj.a.b[0].name)
// 可选链写法
console.log(obj?.a?.b?.[0]?.name); // obj
console.log(obj?.b?.c?.d) // undefined
大大的简化了判断,用起来可是

这里如果 ?. 判断的对象是 nullish(要么是
undefined
, 要么是 null
) 值的话。表达式就会短路(不在往后执行),返回 undefined
如果配合空值合并运算符用起来~
const obj = { a : { name: 'obj'} }
obj?.a?.b ?? 'hello world' // hello world
这里也有要注意的点,空值合并运算符只有左边是 nullish
的时候,才会返回右边的表达式。
而我们以前经常是有的逻辑或 ||
操作,则是如果左边为假值,就返回右边的表达式。
如何在项目中使用
Node
Node.js 的话,可以在 v14.0.0 或以上版本获得原生支持。
前端项目
可以借助 babel 插件 来体验。
安装
yarn add @babel/plugin-proposal-optional-chaining --dev
使用
{
"plugins": ["@babel/plugin-proposal-optional-chaining"]
}
Vue 模板中
很遗憾,暂不支持~看尤大的了。
基本使用
读取对象属性
const obj = { a: { name: 'obj' } }
obj?.a?.name // obj
读取对象属性,动态 key
const obj = { a: { name: 'obj' } }
obj?.a?.['name'] // obj
数组下标访问
const skills = ['CSS', 'HTML', 'JavaScript', 'Vue']
skills?.[3] // Vue
函数调用
const obj = {
fun() {
console.log('hello world')
}
}
obj?.fun?.() // hello world
进阶使用
代替三元运算符
foo ? foo.bar : defaultValue // before
foo?.bar ?? defaultValue // after
简化正则表达式取值
// before
let match = "#C0FFEE".match(/#([A-Z0-9]+)/i)?.[1]
let hex = match && match[1]
// after
let hex = "#C0FFEE".match(/#([A-Z0-9]+)/i)?.[1]
属性检查
// before
if (element.prepend) element.prepend(otherElement);
// after
element?.prepend(otherElement)
避免过度使用
我们虽然有了锤子🔨,但是不能看到什么都看作是钉子。
if 判断之后需要执行多个操作
// before
if (foo) {
something(foo.bar);
somethingElse(foo.baz);
andOneLastThing(foo.yolo);
}
// after
something(foo?.bar);
somethingElse(foo?.baz);
andOneLastThing(foo?.yolo);
这里重构后的代码,我们会从原来只会判断一次,变成需要判断三次...而且没增加一个属性,一个方法又要再判断一次。

所以也不能随便乱用啊,亲!
省去赋值判断
// before
if (foo && foo.bar) {
foo.bar.baz = someValue;
}
// after
foo?.bar?.baz = someValue
看到这代码,是不是以为是个常规正常操作?

运行一下

很明显的,一个运行错误狠狠的甩在了我们的脸上~
所以可选链虽好,也不能乱用呀!
使用陷阱
判断严格相等
首先我们来看一段代码
if (foo?.bar === baz) {
// todo
}
看了看,好像没什么问题啊?

因为隐藏的
Bug
还得满足条件才会触发 (不过可能有些朋友会说,那不是 Bug
,而是我留下的彩蛋.....)- 当
foo
不存在的时候,可选链表达式会返回undefined
- 当
baz
变量的值为undefined
的时候
恭喜你,逻辑被运行了,同理还有判断不等于的时候也会有这种问题。
判断非严格相等
先看代码
if (foo?.bar !== SomeValue) {
// TODO
}
上面说了,如果可选链表达式短路会返回一个 undefined
,那这时候,如果 someValue
是一个其他值的话,那你可能也在你自己代码里面埋下了个彩蛋..
举个例子 undefined !== 'string'
...
结语
本篇文章介绍了什么是可选链,以及基本语法,和列举了部分使用场景,相信这么聪明的你肯定已经学会了!
下次再遇到后端丢过来的深层嵌套对象也不怕啦~