为什么在TypeScript中要使用三个等号(===)?

77
在JavaScript中,通常建议使用===而不是==,这是众所周知的最佳实践。
在TypeScript中,哪种方式更优?是否有一种比另一种更可取?
在我看来,在TypeScript中使用===没有意义,因为比较已经仅适用于相等类型,因此你将不会像在纯JavaScript中那样进行(更多或更少好玩的)强制转换。如果暂时放下与JavaScript的兼容性不谈,TypeScript甚至可以去掉===,不是吗?

3
如果放弃与 JavaScript 的兼容性,许多东西都可以得到改进,但我认为 TypeScript 的设计师们更看重保持与 JavaScript 尽可能的接近,同时加入类型定义。 - kshetline
3
我认为它不会将==转换为===,因为TypeScript也有一个类型叫做 any - Vasileios Pallas
8
这似乎是一个主观/意见问题,因此我不知道它是否可能有一个权威的答案。我的意见是:• TypeScript旨在支持良好的JS实践,因为有效的JS代码也是有效的TS代码(可能会有警告),因此始终以原样发出(除非针对较旧版本的JS),所以你基本上想保留“===”,这既是有效的JS,也被认为是最佳实践。•如果你有两个类型为“string | number”的表达式,在TypeScript中你可以使用“==”进行比较,但是当“'3'==3”为真时,你可能仍然会感到惊讶。 - jcalz
3
@jcalz,这里的假设建议是通过使TypeScript的==“意味着相同的东西”与JavaScript的===来摆脱===,从而使==转译为===。这不是一项依赖类型检查的提案,同时仍然使用==的旧含义。 - kshetline
1
如果这是提议的话,那么可以指向 TypeScript 的设计目标 #7 作为权威理由,说明为什么不能发生...TS 应该保留 JS 代码的运行时行为,这意味着如果你在 TS 中键入 (Math.random() < 0.5 ? 3 : "3") == 3;,则生成的 JS 在运行时必须始终评估为 true - jcalz
显示剩余6条评论
4个回答

74

简短版:

== 运算符会进行意外的类型转换,在 JavaScript 中 1=="1"true。使用 === 运算符可以避免这种情况。用 === 进行不同类型的比较总是返回 false

TypeScript 编译器将在你使用 == 进行不同类型比较时发出错误信息。这消除了在 JavaScript 中使用 == 可能发生的意外类型转换。

这是一个在 TypeScript 编译器中会导致错误信息的 JavaScript 有效用法。所有有效的 JavaScript 也适用于 TypeScript 的观点是一个常见的误解。这只是不正确的。

详细版

我认为接受的回答是误导性的。Typescript 实际上确实修复了 =====(至少尽可能地修复)。

在 JavaScript 中有两个比较运算符:

  • ==: 当比较原始值(如数字和字符串)时,此运算符将在执行比较前应用类型转换。 1 == "1" 的结果为 true
  • ===: 此运算符不会进行类型转换。如果类型不匹配,它将始终返回 false

此外,两个运算符都会基于引用来比较引用类型。即使两个独立的对象存储相同的值,它们也永远不被视为相等:

let a = {val:1};
let b = {val:1};
c = a;

a==b; // false
a===b; // false
a==c; //true
a===c; //true

那么在JavaScript比较中,有两种常见的错误来源:

  1. 使用==比较不同类型的值可能导致意想不到的类型转换。
  2. 比较对象和数组是基于引用而非存储在内部的值。

正如现有答案所说,Typescript被设计为Javascript的超集。因此,它不会改变这些比较运算符的行为。如果您在Typescript中编写==,则会进行类型转换。

那么如何解决这个问题?使用编译器。如果您确实编写了将不兼容的类型与==进行比较的代码,则会出现编译器错误。尝试编译以下示例:

let str = "1";
let num = 1;

console.log(str == num);
编译器会告诉你:
comparisons.ts:4:13 - error TS2367: This condition will always return 'false' since the types 'string' and 'number' have no overlap.

4 console.log(str == num);
              ~~~~~~~~~~

Found 1 error.

有一个常见的误解,认为任何有效的JavaScript代码也是有效的TypeScript代码。这是不正确的,在上面的代码示例中,TypeScript编译器会抱怨有效的JavaScript代码。

这解决了两个错误源中的第一个:意外的类型转换。但它并未涉及第二个错误源:基于引用的比较。据我所知,当您想要基于对象存储的值进行比较时,您无法使用这些运算符。您将不得不实现自己的equals()方法。

此外,您可能已经注意到编译器错误是错误的。比较不总是评估为false。我认为这是TypeScript中的一个bug,并已经提出了问题


26

假设你从头设计TypeScript,你的首要目标就是优化编写更加安全的代码(TypeScript设计目标1),但有一些限制会阻止你完全实现这个目标。

JavaScript兼容性(TypeScript设计目标7)

JavaScript应该可以在不改变任何内容的情况下作为有效的TypeScript代码运行。

CoffeeScript没有关于这方面的保证,因此它可以将所有的==替换成===,并告诉用户“不要依赖==的行为”。TypeScript不能重新定义==,否则会破坏所有依赖其行为的JavaScript代码(尽管这对第3个目标有悲惨的影响)。

这也意味着TypeScript不能改变===的功能,例如,在编译时检查两个操作数的类型并拒绝比较不同类型的变量。

此外,兼容性不仅限于JavaScript程序;破坏兼容性也会影响JavaScript程序员,打破他们对=====之间差异的假设。请参见TypeScript非目标7

  

引入可能会使用户感到惊讶的行为。而是对其他常用语言采用考虑周全的模式。

JavaScript作为编译目标(TypeScript设计目标4)

所有的TypeScript必须能够表示为JavaScript。此外,尽可能使用惯用的JavaScript语法。

然而,TypeScript编译器完全可以使用返回布尔值的方法来进行所有比较,摒弃=====。这对用户来说甚至更加安全:在每个TypeScript类型上定义一个类型安全的等式方法(有点像C++中的operator==,只是没有重载)。

因此,有一个解决方法(用于比较类的用户)。在使用类型安全的等式方法之前,unknownany变量可以被缩小其类型。

应该优先选择哪种方式

在您使用JavaScript中===的地方都要使用它。这样做的好处是避免了==常见的陷阱,并且不需要维护额外的方法。TypeScript编译器的输出将接近于惯用的JavaScript。使用==存在与JavaScript非常相似的陷阱,特别是涉及any[]{}时。 作为例外,如果库代码不一致,使用== null来检查nullundefined可能会避免一些问题。

一种用于引用相等的方法(类似 === 的行为)可能会与深度/值递归相等性检查混淆。此外,在TypeScript中广泛使用 === ,使您的代码符合约定通常比任何小的类型安全性更重要。


2
"JavaScript 应该可以在不进行任何更改的情况下,被视为有效的 TypeScript。但是需要进一步定义“有效”。尽管它将生成相同的代码,但如果启用严格检查,它允许向您提供错误。设计目标仅说明运行时不会改变。构建时间方面没有任何保证。" - Vanuan
1
请问您能否演示一下在TypeScript中允许使用"=="不当的例子? - Vanuan
在 TypeScript 中,无论何时都要使用 ===。这仅适用于那些已经了解 JavaScript 的人来说。Java 或 C# 程序员可能会直接选择 TypeScript,因为它更具有熟悉的类型严格性。 - Nyerguds

4
你的直觉是正确的,在TypeScript中,===并没有太多的价值。关于“编译到JS”的论点并不真实有效。TS确保两个操作数具有相同的类型。当两个操作数具有相同的类型时,=====表现完全一样。例如:
const s : string = "s"
const n : number = 1
console.log(s == n)

TS2367: This condition will always return 'false' since the types 'string' 
and 'number' have no overlap

因此,除非你的代码散布着any或者你会在编译后修改JS,否则仅仅将2eq替换为3eq并不会带来任何好处。

人们只是在寻找保持使用===的习惯的方法,以理智化。但是,时不时重新考虑自己的原则是一种很好的实践。

从我的个人经验来看,即使在JS中,我也从来没有遇到过==的任何问题。这个“陷阱”被那些从其他语言转到JS的人夸大了,但实际上并不是那么重要。将假阳性换成假阴性或反之并不能使代码自动变得更好。

你最终可以发现像Ryan Florence或Kyle Simpson这样的顶级作者在他们的代码中同时使用==和===-适用于特定情况。所以不要盲目地跟随“最佳实践者”和默认的linter设置。他们错了很多次。


-1

我的观点是应该始终使用===

第一条理由:TypeScript不会将==转换为===。有些TypeScript翻译器只是剥离类型。因此,如果由于某种原因忘记对程序进行类型检查或者您(或代码的未来维护者)使用类型转换来覆盖类型安全性,则在所有地方使用===会导致更健壮的代码。虽然不应该发生这种情况,但很多事情都不应该发生。

第二条理由:null == undefined。这在TypeScript中也是正确的。我认为,如果一个人编写if (x == null),它会使代码变得不太可读,因为它暗示了对undefined的检查,而隐式代码比显式的if (x === null || x === undefined)不易读。如果这不是故意的,还可能出现微妙的错误。

我认为,在无条件地使用===时,除了审美偏好之外,没有任何问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接