ES 提案:JavaScript 的空值合并(nullish coalescing)

这篇博客文章描述 Gabriel Isenberg 提出的 ECMAScript 功能提案“Nullish coalescing for JavaScript”。它提出使用 ?? 操作符替代 || 来提供默认值。

概述

实际值的 ?? 操作结果取默认值的情况是:

  • 如果值是 nullundefined 时取默认值。
  • 其它情况就是值本身。

举例:

undefined ?? 'default'; // 'default'
null ?? 'default'; // 'default'

false ?? 'default'; // false
'' ?? 'default'; // ''
0 ?? 'default'; // 0

以前:通过 || 获取默认值

下面的两个表达式是等价的,总结文章the logical Or operator ||查看更多。

a || b;
a ? a : b;

意思是:

  • 如果 a 是真值(truthy[1]),那么 a || b 的结果是 a
  • 其它情况结果为 b

这使得可以使用 || 指定在实际值为假值(falsy[2]) 时需要使用的默认值:

const result = actualValue || defaultValue;
function getTitle(fileDesc) {
  return fileDesc.title || '(Untitled)';
}
const files = [{ path: 'index.html', title: 'Home' }, { path: 'tmp.html' }];

console.log(files.map((f) => getTitle(f)));
// => ['Home', '(Untitled)']

注意,只有实际值是 nullundefined 时才使用默认值。需要这样,是因为 nullundefined 都为假值(falsy):

undefined || 'default'; // 'default'
null || 'default'; // 'default'

并且,当实际值是任何的假值(falsy)时默认值也会被使用,举例:

false || 'default'; // 'default'
'' || 'default'; // 'default'
0 || 'default'; // 'default'

于是,代码 getTitle() 不能正确运行:

getTitle({ path: 'empty.html', title: '' }) === '(Untitled)';

空值合并操作符 ??

当要提供默认值时,空值合并操作符 ?? 用来替换逻辑运算符和 || 操作符。下面两个表达式是等价的:

a ?? b;
a !== undefined && a !== null ? a : b;

默认值像这样使用:

const result = actualValue ?? defaultValue;

对于 undefinednull?? 操作符与 || 操作符结果相同:

undefined ?? 'default'; //'default'
null ?? 'default'; // 'default'

然而,对于其它左侧的操作数,它不会返回默认值,即使它们的值是假值(falsy):

false ?? 'default'; // false
'' ?? 'default'; // ''
0 ?? 'default'; // 0

让我们使用 ?? 重写 getTitle()

function getTitle(fileDesc) {
  return fileDesc.title ?? '(Untitled)';
}

现在运行它获取 .title 是期望的那样返回空字符串:

getTitle({ path: 'empty.html', title: '' }) === '';

通过解构获取默认值

getTitle() 使用 ?? 操作符简单方法来实现。注意,你也可以使用解构实现它:

function getTitle({ title = '(Untitled)' }) {
  return title;
}

使用 ?? 操作符的真实例子

一个真实例子,我们使用 ?? 简化接下来的方法。字符串方法 .match() 的说明文章“JavaScript for impatient programmers”,点击查看更多。

function countMatches(regex, str) {
  if (!regex.global) {
    throw new Error('Regular expression must have flag /g: ' + regex);
  }
  const matchResult = str.match(regex); // null or Array
  if (matchResult === null) {
    return 0;
  } else {
    return matchResult.length;
  }
}

console.log(countMatches(/a/g, 'ababa')); // 3
console.log(countMatches(/b/g, 'ababa')); // 2
console.log(countMatches(/x/g, 'ababa')); // 0

countMatches(/a/, 'ababa'); // Error

如果使用 ?? 操作符,代码如下:

function countMatches(regex, str) {
  if (!regex.global) {
    throw new Error('Regular expression must have flag /g: ' + regex);
  }
  return (str.match(regex) ?? []).length;
}

空值合并和可选链(optional chaining)

空值合并操作符 ?? 被明确设计为补充属性访问的可选链。举例来说,在下面的代码中,它们两个在行 A 中都使用了。





















 



const persons = [
  {
    surname: 'Zoe',
    address: {
      street: {
        name: 'Sesame Street',
        number: '123',
      },
    },
  },
  {
    surname: 'Mariner',
  },
  {
    surname: 'Carmen',
    address: {},
  },
];

const streetNames = persons.map((p) => p.address?.street?.name ?? '(no name)'); // (A)

console.log(streetNames);
// ["Sesame Street", "(no name)", "(no name)"]

实践

扩展阅读

译者注
[1] truthy 不是 true,详见 MDN 的解释。
[2] falsy 不是 false,详见 MDN 的解释。

英文原文:https://2ality.com/2019/08/nullish-coalescing.html

Copyright © 2019 by Wu WenJun