TypeScript 3.7增加了对?? 操作符的支持,它被称为nullish coalescing操作符。我们可以使用这个操作符来为一个可能是null 或undefined 的值提供一个回退值。
JavaScript中的Truthy和Falsy值
在我们深入研究?? 操作符之前,让我们回顾一下,JavaScript的值可以是真实的,也可以是虚假的:当胁迫为一个布尔值时,一个值可以产生true ,也可以产生false 。 在JavaScript中,以下值被认为是虚假的。
false0-00nNaN""nullundefined
所有其他的JavaScript值在被胁迫为布尔值时都会产生值true ,因此被认为是真实的。
用?? 操作符提供回避值
?? 操作符可以用来提供一个后备值,以防另一个值是null 或undefined 。它需要两个操作数,写法如下。
value ?? fallbackValue;
如果左边的操作数是null 或undefined ,?? 表达式就会评估到右边的操作数。
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 属性包含值null 或undefined 的情况下提供默认值true 。如果prettyPrint 包含值false ,表达式false ?? true 仍然评估为false ,这正是我们在这里想要的行为。
请注意,在这里使用|| 操作符会导致不正确的结果。对于值null 和undefined ,以及值false ,options.prettyPrint || true 会评估为true 。这显然不是我们的本意。我在实践中看到过这种情况,所以一定要记住这种情况,并使用?? 操作符来代替。
编译的输出 ES2020和更新的版本
nullish凝聚运算符已经达到了TC39进程的第四阶段("完成"),现在是ES2020的正式组成部分。因此,当你在你的tsconfig.json文件中针对"ES2020" (或更新的语言版本)或"ESNext" ,TypeScript编译器将原封不动地发出?? 运算符,而不进行任何降级。
{
"compilerOptions": {
"strict": true,
"target": "ES2020"
}
}
因此,这个简单的表达式将被释放出来,没有任何变化。
value ?? fallbackValue;
如果你打算在针对"ES2020" 或较新的语言版本时使用?? 操作符,请前往caniuse.com和node.green,并确保你需要支持的所有JavaScript引擎都已实现该操作符。
JavaScript输出 ES2019和更早的版本
如果你在你的tsconfig.json文件中针对"ES2019" 或更早的语言版本,TypeScript编译器会将nullish凝聚运算符改写为条件表达式。这样一来,我们今天就可以开始在代码中使用?? 操作符,而且编译后的代码仍然可以在旧的JavaScript引擎中成功解析和执行。
让我们再看看同一个简单的?? 表达式。
value ?? fallbackValue;
假设我们的目标是"ES2019" 或更低的语言版本,TypeScript编译器将发出以下JavaScript代码。
value !== null && value !== void 0 ? value : fallbackValue;
value 变量与null 和undefined (表达式的结果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 变量与null 和void 0 进行比较,并(有可能)作为整个表达式的结果值使用。这个中间变量是必要的,这样getValue 函数就只被调用一次。
编译后的输出 检查null 和undefined
你可能想知道为什么编译器会发出下面的表达式来检查value 变量与null 和undefined 。
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 不被认为严格等于null 或undefined ,但它被认为松散地等于null 和undefined 。由于这种反常现象,TypeScript编译器不能发出value != null 作为检查,因为它将产生不正确的结果document.all 。
你可以在Stack Overflow上的 "Why is document.all falsy? "问题的答案中阅读更多关于这一奇怪行为的信息。哦,我们为网络兼容性所做的事情。