可选链运算符(?.)二三事

126 阅读2分钟

场景再现

代码审核中~~

同事代码:

a?.b.c();

我: 哥们你这代码不对呀,?.后面的引用都要用?.呀,不然a为undefined还是要报错吧。应该写成:

a?.b?.c();

同事:😳

我:还不服气?给你打开控制台试试:

image.png

我:😳

然后同事下班了,于是便有了这篇文章。

(?.)原理分析

a?.b.c

我直接打开babel,编译一波:

// 源码
a?.b.c
// babel后
var _a;
(_a = a) === null || _a === void 0 ? void 0 : _a.b.c;

这就看的很清晰了:

  1. ?.运算符在根对象为null或者undefined生效.
  2. 只验证?.的对象,该对象为空则直接返回undefined.

a?.b.c()

再来看看代码审核中的场景:

// 源码
a?.b.c()
// babel
var _a;
(_a = a) === null || _a === void 0 ? void 0 : _a.b.c();
  1. 从编码后的代码可以看出,就算是调用函数也无所谓,根对象为空直接返回了,并不会执行
  2. 这就涉及到了短路计算的场景,例如:
let potentiallyNullObj = null;
let x = 0;
let prop = potentiallyNullObj?.[x++];

这种情况,x++并不会执行,x的值也不会变化。

a.b?.[0]

数组前也可以用?.运算符

// 源码
a.b?.[0]
// babel
var _a$b;
(_a$b = a.b) === null || _a$b === void 0 ? void 0 : _a$b[0];

a?.[0]逻辑其实和a?.b类似,只是换成了数组写法而已。

a.b?.()

函数调用前也可以用?.运算符

// 源码
a.b?.()
// babel
var _a$b, _a;
(_a$b = (_a = a).b) === null || _a$b === void 0 ? void 0 : _a$b.call(_a);

这种方法适用于a.b为undefinednull的情况。

a = { b: undefined }
// 报错 a?.b is not a function
a?.b()
// 返回undefined
a.b?.()

从bebel编译出的源码也很好解释,a?.b()只判断a,a不为空还是会执行a.b(),但是b也是空的,无法执行,遂报错。

反思

关于?.的原理好歹是理清了,但是为啥我之前会有?.后都需要用?.的错误印象呢?

后经过一番反思,之前应该是遇到了如下情况:

a: {
    b: { c: any } | undefined
} | undefined

也就是说a和b都可能是undefined, 如果只写成a?.b.c这时候如果b为空,则依然会报错,所以必须写成a?.b?.c。只是之前不求甚解,故有此一劫。