ChatGPT解决这个技术问题 Extra ChatGPT

在 Typescript 中,什么是 ! (感叹号/砰)操作员取消引用成员时?

在查看 tslint 规则的源代码时,我遇到了以下语句:

if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

注意 node.parent 之后的 ! 运算符。有趣的!

我首先尝试使用我当前安装的 TS (1.5.3) 版本在本地编译文件。产生的错误指向了爆炸的确切位置:

$ tsc --noImplicitAny memberAccessRule.ts 
noPublicModifierRule.ts(57,24): error TS1005: ')' expected.

接下来我升级到最新的 TS (2.1.6),编译它没有问题。所以它似乎是 TS 2.x 的功能。但是转译完全忽略了爆炸,导致以下JS:

if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

到目前为止,我的 Google fu 让我失望了。

TS 的感叹号运算符是什么,它是如何工作的?

你的团队中有一个懒惰的开发人员^^;如果没有类型定义,您的 IDE 会给您一条错误消息,或者该道具可能在运行时未定义。 ! 将绕过此错误消息,类似于:“我告诉你,它会在那里。”

L
Louis

那是非空断言运算符。这是一种告诉编译器“这里的表达式不能是 nullundefined,所以不要抱怨它可能是 nullundefined”的一种方式。有时类型检查器本身无法做出决定。

解释here

一个新的!后缀表达式运算符可用于在类型检查器无法得出该事实的上下文中断言其操作数为非空且非未定义。具体来说,操作 x!产生一个 x 类型的值,其中排除了 null 和 undefined。类似于 x 和 x as T 形式的类型断言,!非空断言运算符在发出的 JavaScript 代码中被简单地删除。

我发现在该解释中使用“断言”一词有点误导。从开发人员断言它的意义上说,它是“断言”,而不是要执行测试的意义上。最后一行确实表明它不会发出任何 JavaScript 代码。


很好的解释。我发现在有问题的变量之后添加 ! 之前对其执行 console.assert() 是一种很好的做法。因为 add ! 告诉编译器忽略空检查,所以它在 javascript 中编译为 noop。因此,如果您不确定变量是否为非空,那么最好进行显式断言检查。
作为一个激励示例:将新的 ES Map 类型与 dict.has(key) ? dict.get(key) : 'default'; 之类的代码一起使用,TS 编译器无法推断 get 调用永远不会返回 null/undefined。 dict.has(key) ? dict.get(key)! : 'default'; 正确缩小类型。
该运算符是否有俚语,例如猫王运算符如何引用二元运算符?
@ebakunin“爆炸操作员”,正如您在迈克的回答中看到的那样
@ebakunin,Elvis ?. AFAIK 来自 C# 领域。对于可空类型,C# got its bang too(当然是双关语)。是的,托尼爵士的发明对程序编程世界造成了严重破坏,我们仍在清理余波。作为最可爱的人,他仍然为此道歉。奇怪的是,他对 CS 的主要贡献是关于程序正确性的自动推理(例如,Hoare 逻辑),应用于静态代码分析:他发明了 null 和静态捕获它的方法! :)
M
Mike Chamberlain

路易斯的回答很好,但我想我会尽量简明扼要地总结一下:

bang 运算符告诉编译器暂时放宽它可能要求的“非空”约束。它对编译器说:“作为开发人员,我比你更清楚这个变量现在不能为空”。


或者,作为编译器,它搞砸了。如果构造函数没有初始化属性,但生命周期钩子完成了它并且编译器无法识别这一点。
这不是 TS 编译器的责任。与其他一些语言(例如 C#)不同,JS(以及因此 TS)不要求在使用前初始化变量。或者,换个角度来看,在 JS 中,所有用 varlet 声明的变量都被隐式初始化为 undefined。此外,类实例属性可以这样声明,因此 class C { constructor() { this.myVar = undefined; } } 是完全合法的。最后,生命周期钩子依赖于框架;例如 Angular 和 React 以不同的方式实现它们。所以不能指望 TS 编译器对它们进行推理。
@EugeneKarataev 是的,框架经常在自己内部初始化变量,而 ts 控制流分析无法捕捉到它。它的使用肯定会减少,但是您会遇到需要它的实例。
@EugeneKarataev 一件事的可读性。感叹号告诉代码的读者:THIS CANNOT BE NULL。 (对不起大写)。虽然 ? 说:这可能是 null,但事实并非如此(因此,如果您绝对知道它不是 null,则只能使用 !
@EugeneKarataev 不同之处在于 ?. 返回类型是可以为空的(即使它永远不会发生),并且您将不得不处理一个可以为空的类型。如果您知道 null 是不可能的,!. 会一劳永逸地修复类型。
S
Sumit

非空断言运算符

使用非空断言运算符,我们可以明确地告诉编译器一个表达式的值不是 nullundefined。当编译器无法确定地推断类型但我们比编译器拥有更多信息时,这可能很有用。

例子

TS代码

function simpleExample(nullableArg: number | undefined | null) {
   const normal: number = nullableArg; 
    //   Compile err: 
    //   Type 'number | null | undefined' is not assignable to type 'number'.
    //   Type 'undefined' is not assignable to type 'number'.(2322)

   const operatorApplied: number = nullableArg!; 
    // compiles fine because we tell compiler that null | undefined are excluded 
}

编译好的JS代码

请注意,JS 不知道非空断言运算符的概念,因为这是 TS 功能

"use strict";
function simpleExample(nullableArg) {
    const normal = nullableArg;
    const operatorApplied = nullableArg;
}

M
Masih Jahangiri

简答

非空断言运算符 (!) 帮助编译器确定此变量不是空变量或未定义变量。

let obj: { field: SampleType } | null | undefined;

... // some code

// the type of sampleVar is SampleType
let sampleVar = obj!.field; // we tell compiler we are sure obj is not null & not undefined so the type of sampleVar is SampleType

h
hfutsora

我的理解是 ! 运算符与 NonNullable 做同样的事情。

let ns: string | null = ''
//  ^? let ns: string | null
let s1 = ns!
//  ^? let s1: string
let s2 = ns as NonNullable<typeof ns>
//  ^? let s2: string