船新版本的JavaScript(一):?.(Optional Chaining) 和 ??(Nullish Coalescing Operator)

大扎好,我系Iron。Babel 7,介四里没有挽过的船新版本,挤需体验三番钟,里造会干我一样,爱上这门语言

?.可选链调用(Optional Chaining)

背景

我们经常会遇到这样的情况,我们要去读取一个嵌套的对象中的某个深层的值,但我们必须检验我们访问的“路径”的每个节点是否为nullundefined,这种情况下我们一般是这么来的

const street = user.address && user.address.street;

如果是foo.bar.biz.bah这样的路径,那么我们得这么写

const bah = foo && foo.bar && foo.bar.biz && foo.bar.biz.bah

这个提案出现就是为了避免使用 && 或者||并不断重复“访问路径"中的属性名(就是上面不断出现的foo, bar, biz

语法

// 带空指针检查地取静态属性
a?.b
// 等效于
a == null ? undefined : a.b
// NOTE: null == undefined 会返回ture

// 取动态的属性
a?.[x]
// 将被转化为
a == null ? undefined : a[x]


// 检查一个函数的引用是否为空,非空则调用它,否则直接返回undefined
// 当然这也可能会抛出异常,显然会出现func既不是函数也不是null也不是undefind的情况
func?.()
//
func == null ? undefined : func()

短路

?.运算符具有短路的特性。?.左边的值为nullundefined,那么它右边的表达式将不会执行

a?.[++x]
// 等效于
a == null ? undefined : a[++x]
// 当a为null或者undefined时++x不会执行,x不变

当短路发生时,不仅短路当前?.的右值,?.之后的所有表达式都会被短路掉

a?.b.c(++x).d
// 后面的`b.c(++x).d`被短路,++x不会被执行,上面这段代码等效于
a == null ? undefined : a.b.c(++x).d

边界:括号

括号会为?.的短路行为定一个边界,括号外的调用将不会被短路

(a?.b).c
// `.c`在a为null或者undefined的时候也会被调用,因此可能会引发空指针异常
(a == null ? undefined : a.b).c

delete

delete a?.b
// 我们会得到下面的
a == null ? undefined : delete a.b
// 不过delete删掉一个null或者undefined也不会报错

??(空值合并)(Nullish Coalescing Operator)

背景

首先,我们先下个定义,一个Nullish的值,即这个值为nullundefined,一个falsy的值,即在Boolean上下文中可被认定为false 的值,他们是('', false, 0, null, undefined, NaN, document.all) 当我们用上面所说的?.(optional chaining operator)运算符去读取属性有时会返回nullundefined,这个时候我们可能需要一个默认值 举个例子 假设我们有这么一个对象

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false
  }
};

似乎我们可以用||运算符给?.(optional training operator)的结果指定一个默认值

const undefinedValue = response.settings?.undefinedValue || 'some other default'; // 结果是'some other result'
const nullValue = response.settings?.nullValue || 'some other default'; // 结果也是'some other result'

上面两种情况可以按照我们想象的方式工作 但下面三种情况,?.运算符的结果为falsy但不为nullish,但最后还是可能返回||的右值

const headerText = response.settings?.headerText || 'Hello, world!'; // 当`||`左值为''(空字符串)时,表达式结果:'Hello, world'
const animationDuration = response.settings?.animationDuration || 300; // 左值为0时,表达式结果: 300
const showSplashScreen = response.settings?.showSplashScreen || true; // 左值为false时,表达式结果为true

因此使用||?.的结果指定默认值时,当左值为falsy且不是nullish,||运算依旧会返回右边的默认值,这与我们所希望的,只有当?.结果为nullish时才返回一个默认值的意愿是相左的。 当然我们可以直接用三目运算符避免这种情况。??运算符的出现是为了更好地处理左值为非nullish但为falsy时需要返回左值的情况

语法

这个运算符的语法非常简单 以上面的例子为例,??运算符的左值为null或undefined时返回右值,否则返回左值

const undefinedValue = response.settings?.undefinedValue ?? 'some other default'; // some other default'
const nullValue = response.settings?.nullValue ?? 'some other default'; // 'some other default'
// 下面三个左值为非null且非undefined,返回左值
const headerText = response.settings?.headerText ?? 'Hello, world!'; // : ''
const animationDuration = response.settings?.animationDuration ?? 300; //  0
const showSplashScreen = response.settings?.showSplashScreen ?? true; // false

如何在代码中用上?.??

babel已经实现了这两个语法,他们可以用babel7的插件来编译到兼容的语法上

安装

npm install --save-dev @babel/plugin-proposal-optional-chaining
npm install --save-dev @babel/plugin-proposal-nullish-coalescing-operator
# 用yarn装也无所谓

配置

在.babelrc里加上这么两句

{
  "plugins": ["@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-optional-chaining"]
}

如果有webpack的话需要在webpack 中babel-loader的options中加上

{
  loader: 'babel-loader',
  options: {
    plugins: [
      require('@babel/plugin-proposal-nullish-coalescing-operator'),
      require('@babel/plugin-proposal-optional-chaining')
      ]
 }
}

使用

最后在命令行里敲webpack或者babel就行了

参考资料

Babel 7 release announcement

TC39 optional chaining proposal

TC39 nullish coalescing operator proposal

@babel/plugin-proposal-optional-chaining

babel/plugin-proposal-nullish-coalescing-operator