•  阅读 4 分钟

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
// eslint-disable-next-line no-unneeded-ternary
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 的解释。

本文由 吳文俊 翻译,原文地址 ES2020: Nullish coalescing for JavaScript

> cd ..