在 Typescript 中,啥是 ! (感叹号/爆炸)操作员取消引用成员时?

Posted

技术标签:

【中文标题】在 Typescript 中,啥是 ! (感叹号/爆炸)操作员取消引用成员时?【英文标题】:In Typescript, what is the ! (exclamation mark / bang) operator when dereferencing a member?在 Typescript 中,什么是 ! (感叹号/爆炸)操作员取消引用成员时? 【发布时间】:2017-12-05 21:48:47 【问题描述】:

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

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

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

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

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

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

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

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

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

【问题讨论】:

【参考方案1】:

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

说明here:

新的! 后缀表达式运算符可用于在类型检查器无法得出该事实的上下文中断言其操作数为非空且非未定义。具体来说,操作x! 产生x 类型的值,其中排除了nullundefined。与 <T>xx 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 "The bang operator",正如您在 Mike 的回答中看到的那样 @ebakunin, Elvis ?. AFAIK 来自 C# 领域。对于可空类型,C# got its bang too(当然是双关语)。是的,托尼爵士的发明对程序编程世界造成了严重破坏,我们仍在清理余波。作为最可爱的人,他仍然为此道歉。奇怪的是,他对 CS 的主要贡献是关于程序正确性的自动推理(例如,Hoare 逻辑),应用于静态代码分析:他发明了 null 和静态捕获它的方法! :)【参考方案2】:

Louis 的回答很棒,但我想我会尽量简明扼要地总结一下:

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

【讨论】:

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

非空断言运算符

使用非空断言运算符,我们可以明确地告诉编译器一个表达式的值不是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 不知道 Non-null 断言运算符的概念,因为这是 TS 功能

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

【讨论】:

以上是关于在 Typescript 中,啥是 ! (感叹号/爆炸)操作员取消引用成员时?的主要内容,如果未能解决你的问题,请参考以下文章

在 Typescript 中,啥是 ! (感叹号/爆炸)操作员取消引用成员时?

带有 Typescript 和 ThemeProvider 的样式化组件。啥是正确的类型?

TypeScript,啥是对象文字的调用签名以及它们如何与泛型类型一起使用?

使用 typescript,啥是最好的 Observable 类型来最好地表示没有内容的 HTTP 响应?

TypeScript 中的问号+冒号双问号问号+点感叹号+点

TypeScript 中的问号+冒号双问号问号+点感叹号+点