TypeScript中的"?"操作符的实例教程

147 阅读5分钟

TypeScript 3.7增加了对?? 操作符的支持,它被称为nullish coalescing操作符。我们可以使用这个操作符来为一个可能是nullundefined 的值提供一个回退值。

JavaScript中的Truthy和Falsy值

在我们深入研究?? 操作符之前,让我们回顾一下,JavaScript的值可以是真实的,也可以是虚假的:当胁迫为一个布尔值时,一个值可以产生true ,也可以产生false 。 在JavaScript中,以下值被认为是虚假的。

  • false
  • 0
  • -0
  • 0n
  • NaN
  • ""
  • null
  • undefined

所有其他的JavaScript值在被胁迫为布尔值时都会产生值true ,因此被认为是真实的。

?? 操作符提供回避值

?? 操作符可以用来提供一个后备值,以防另一个值是nullundefined 。它需要两个操作数,写法如下。

value ?? fallbackValue;

如果左边的操作数是nullundefined?? 表达式就会评估到右边的操作数。

null ?? "n/a";
// "n/a"

undefined ?? "n/a";
// "n/a"

否则,?? 表达式对左边的操作数进行评估。

false ?? true;
// false

0 ?? 100;
// 0

"" ?? "n/a";
// ""

NaN ?? 0;
// NaN

注意,上面所有的左操作数都是假值。如果我们使用 || 操作符而不是?? 操作符,所有这些表达式都会评估为它们各自的右操作数。

false || true;
// true

0 || 100;
// 100

"" || "n/a";
// "n/a"

NaN || 0;
// 0

这种行为就是为什么你不应该使用|| 操作符来为可忽略的值提供回退值。对于假的值,结果可能不是你想要的或预期的。考虑一下这个例子。

type Options = {
  prettyPrint?: boolean;
};

function serializeJSON(value: unknown, options: Options): string {
  const prettyPrint = options.prettyPrint ?? true;
  // ...
}

表达式options.prettyPrint ?? true 让我们在prettyPrint 属性包含值nullundefined 的情况下提供默认值true 。如果prettyPrint 包含值false ,表达式false ?? true 仍然评估为false ,这正是我们在这里想要的行为。

请注意,在这里使用|| 操作符会导致不正确的结果。对于值nullundefined ,以及值falseoptions.prettyPrint || true 会评估为true 。这显然不是我们的本意。我在实践中看到过这种情况,所以一定要记住这种情况,并使用?? 操作符来代替。

编译的输出 ES2020和更新的版本

nullish凝聚运算符已经达到了TC39进程的第四阶段("完成"),现在是ES2020的正式组成部分。因此,当你在你的tsconfig.json文件中针对"ES2020" (或更新的语言版本)或"ESNext" ,TypeScript编译器将原封不动地发出?? 运算符,而不进行任何降级。

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2020"
  }
}

因此,这个简单的表达式将被释放出来,没有任何变化。

value ?? fallbackValue;

如果你打算在针对"ES2020" 或较新的语言版本时使用?? 操作符,请前往caniuse.comnode.green,并确保你需要支持的所有JavaScript引擎都已实现该操作符。

JavaScript输出 ES2019和更早的版本

如果你在你的tsconfig.json文件中针对"ES2019" 或更早的语言版本,TypeScript编译器会将nullish凝聚运算符改写为条件表达式。这样一来,我们今天就可以开始在代码中使用?? 操作符,而且编译后的代码仍然可以在旧的JavaScript引擎中成功解析和执行。

让我们再看看同一个简单的?? 表达式。

value ?? fallbackValue;

假设我们的目标是"ES2019" 或更低的语言版本,TypeScript编译器将发出以下JavaScript代码。

value !== null && value !== void 0 ? value : fallbackValue;

value 变量与nullundefined (表达式的结果void 0 )进行比较。如果两个比较结果都是false ,那么整个表达式的评估值为value ;否则,它的评估值为fallbackValue

现在,让我们看一个稍微复杂的例子。我们将使用一个getValue() 调用表达式作为?? 操作符的左操作数,而不是一个简单的value 变量。

const value = getValue() ?? fallbackValue;

在这种情况下,编译器将发出以下的JavaScript代码(模数化的空白差异)。

var _a;
const value = (_a = getValue()) !== null && _a !== void 0
  ? _a
  : fallbackValue;

你可以看到,编译器生成了一个中间变量_a ,用来存储getValue() 调用的返回值。然后,_a 变量与nullvoid 0 进行比较,并(有可能)作为整个表达式的结果值使用。这个中间变量是必要的,这样getValue 函数就只被调用一次。

编译后的输出 检查nullundefined

你可能想知道为什么编译器会发出下面的表达式来检查value 变量与nullundefined

value !== null && value !== void 0;

难道编译器不能发出以下更短的检查吗?

value != null;

不幸的是,它不能在不牺牲正确性的情况下这样做。对于JavaScript中几乎所有的值,比较value == null 等于value === null || value === undefined 。对于这些值来说,否定值value != null 等同于value !== null && value !== undefined 。然而,有一个值,这两个检查并不等同,这个值是document.all

document.all === null;
// false

document.all === undefined;
// false

document.all == null;
// true

document.all == undefined;
// true

document.all 不被认为严格等于nullundefined ,但它被认为松散地等于nullundefined 。由于这种反常现象,TypeScript编译器不能发出value != null 作为检查,因为它将产生不正确的结果document.all

你可以在Stack Overflow上的 "Why is document.all falsy? "问题的答案中阅读更多关于这一奇怪行为的信息。哦,我们为网络兼容性所做的事情。