在打字稿中,什么是!解除引用会员时(感叹号/爆炸)运营商?

Mik*_*ain 338 typescript tslint

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

if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}
Run Code Online (Sandbox Code Playgroud)

注意!操作员之后node.parent.有趣!

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

$ tsc --noImplicitAny memberAccessRule.ts 
noPublicModifierRule.ts(57,24): error TS1005: ')' expected.
Run Code Online (Sandbox Code Playgroud)

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

if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}
Run Code Online (Sandbox Code Playgroud)

我的Google fu到目前为止都没有让我失望.

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

Lou*_*uis 516

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

这里解释如下:

可以使用新的!post-fix表达式运算符来断言其操作数在类型检查器无法推断该事实的上下文中是非空且非未定义的.具体而言,该操作x!产生x具有nullundefined排除的类型的值.类似键入的形式断言<T>xx as T,所述!非空断言操作者简单地在所发射的JavaScript代码移除.

我发现在这个解释中使用"断言"一词有点误导.它是"断言"的,即开发人员断言它,而不是在将要执行测试的意义上.最后一行确实表明它不会发出任何JavaScript代码.

  • 很好地呼吁'断言'模棱两可. (69认同)
  • 很好的解释.我发现在一个`!`之后附加一个`!`之前对这个变量做一个`console.assert()`是一个好习惯.因为添加`!`告诉编译器忽略空检查,它会在javascript中编译为noop.因此,如果您不确定该变量是否为非null,那么最好进行显式断言检查. (7认同)
  • 作为一个激励性的例子:使用新的ES Map类型和`dict.has(key)这样的代码?dict.get(key):'default';`TS编译器无法推断`get`调用永远不会返回null/undefined.`dict.has(关键)?dict.get(键)!:'default';`正确缩小类型. (7认同)
  • @ebakunin,Elvis `?.` AFAIK 来自 C# 领域。有了可空类型,[C# 也获得了巨大成功](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/null-forgiving)(双关语,当然是有意的)。是的,托尼爵士的发明对程序编程世界造成了严重破坏,而我们仍在清理其影响。作为最可爱的人,他仍然为此道歉。奇怪的是,他对 CS 的主要贡献是关于程序正确性的自动推理(例如霍尔逻辑),应用于静态代码分析:他发明了 null 以及静态捕获 null 的方法!:) (5认同)
  • 我称之为“相信我兄弟运营商” (3认同)
  • 该运算符是否有俚语,例如 Elvis 运算符如何引用二元运算符? (2认同)
  • @ebakunin“爆炸运算符”,正如您在下面迈克的回答中看到的那样 (2认同)

Mik*_*ain 115

路易斯的答案很棒,但我想我会试着简洁地总结一下:

bang运算符告诉编译器暂时放松它可能需要的"非空"约束.它告诉编译器:"作为开发人员,我比你更了解这个变量现在不能为null".

  • 然后,作为开发人员,你搞砸了. (65认同)
  • 这不是TS编译器的责任.与其他一些语言(例如C#)不同,JS(以及TS)不要求在使用前初始化变量.或者,换一种方式来看,在JS中,用`var`或`let`声明的所有变量都被隐式初始化为`undefined`.此外,类实例属性可以这样声明,因此`class C {constructor(){this.myVar = undefined; `完全合法.最后,生命周期钩子依赖于框架; 例如,Angular和React以不同的方式实现它们.因此,不能指望TS编译器对它们进行推理. (18认同)
  • @EugeneKarataev 可读性是一件事。感叹号告诉代码的读者:THIS CANNOT BE NULL。(抱歉关于上限)。虽然“?”表示:这可能是“null”,但事实并非如此(因此,如果您绝对知道它不是“null”,则只能使用“!”。 (5认同)
  • @EugeneKarataev 是的,框架通常会初始化自身内部的变量,而 ts 控制流分析无法捕获它。它的使用量肯定会减少,但您会遇到需要它的情况。 (4认同)
  • @EugeneKarataev 的区别在于 `?.` 返回类型是可为空的(即使它永远不会发生),并且您必须在以后处理可为空的类型。如果你知道 null 是不可能的,`!.` 可以一劳永逸地修复类型。 (4认同)
  • @arg20 假设变量 `foo` 是由框架初始化的。与“foo?.bar”相比,“foo!.bar”有哪些优点?我只看到一个缺点:“!”运算符可能会在运行时抛出异常,而可选链接则不会。 (3认同)
  • 或者,作为编译器,它搞砸了。如果构造函数未初始化属性,但生命周期挂钩执行了该操作,则编译器无法识别该属性。 (2认同)
  • “作为一名开发人员,我知道这段代码可能会在运行时导致错误,这是我的愿望” (2认同)

Mas*_*iri 42

简答

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

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
Run Code Online (Sandbox Code Playgroud)


Wil*_*een 25

非空断言运算符

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

例子

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 
}
Run Code Online (Sandbox Code Playgroud)

编译的JS代码

请注意,JS 不知道 Non-null 断言运算符的概念,因为这是 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 
}
Run Code Online (Sandbox Code Playgroud)