TypeScript 是习惯于使用其他具有静态类型的语言(如 C# 和 Java)的程序员的热门选择。
TypeScript 的类型系统提供了许多相同的好处,例如更好的代码补全、更早的错误检测以及程序各部分之间更清晰的通信。虽然 TypeScript 为这些开发人员提供了许多熟悉的特性,但值得退一步看看 JavaScript(以及 TypeScript)与传统的 OOP 语言有何不同。了解这些差异将有助于您编写更好的 JavaScript 代码,并避免从 C#/Java 直接转向 TypeScript 的程序员可能遇到的常见陷阱。
协同学习 JavaScript
如果您已经熟悉 JavaScript,但主要是一名 Java 或 C# 程序员,此入门页面可以帮助解释您可能容易遇到的常见误解和陷阱。TypeScript 对类型的建模方式与 Java 或 C# 有很大不同,在学习 TypeScript 时务必牢记这一点。
如果您是一名 Java 或 C# 程序员,并且对 JavaScript 普遍陌生,我们建议您先学习一些没有类型的 JavaScript,以了解 JavaScript 的运行时行为。因为 TypeScript 不会改变代码的运行方式,所以您仍然需要学习 JavaScript 的工作原理,才能编写真正起作用的代码!
务必记住,TypeScript 使用与 JavaScript 相同的运行时,因此有关如何实现特定运行时行为(将字符串转换为数字、显示警报、将文件写入磁盘等)的任何资源都同样适用于 TypeScript 程序。不要局限于 TypeScript 特定的资源!
重新思考类
C# 和 Java 是我们可能称之为强制 OOP 的语言。在这些语言中,类是代码组织的基本单位,也是运行时所有数据和行为的基本容器。强制所有功能和数据都保存在类中对于某些问题来说可能是一个很好的领域模型,但并非每个领域都需要以这种方式表示。
自由函数和数据
在 JavaScript 中,函数可以存在于任何地方,数据可以自由传递,而无需位于预定义的 class
或 struct
中。这种灵活性非常强大。“自由”函数(与类无关的函数)在没有隐式 OOP 层次结构的情况下处理数据,往往是编写 JavaScript 程序的首选模型。
静态类
此外,TypeScript 中不需要 C# 和 Java 中的某些结构,例如单例和静态类。
TypeScript 中的 OOP
也就是说,如果你愿意,你仍然可以使用类!某些问题非常适合通过传统的 OOP 层次结构来解决,而 TypeScript 对 JavaScript 类的支持将使这些模型更加强大。TypeScript 支持许多常见模式,例如实现接口、继承和静态方法。
我们将在本指南的后面部分介绍类。
重新思考类型
TypeScript 对类型的理解实际上与 C# 或 Java 有很大不同。让我们探讨一些差异。
名义具象类型系统
在 C# 或 Java 中,任何给定值或对象都只有一个确切的类型 - null
、基本类型或已知类类型。我们可以调用 value.GetType()
或 value.getClass()
等方法来查询运行时的确切类型。此类型的定义将驻留在某个类中,并具有某个名称,并且我们不能使用两个具有相似形状的类来代替彼此,除非存在显式继承关系或共同实现的接口。
这些方面描述了具象名义类型系统。我们在代码中编写的类型在运行时存在,并且类型通过其声明而不是其结构相关联。
类型作为集合
在 C# 或 Java 中,在运行时类型与其编译时声明之间存在一对一对应关系是有意义的。
在 TypeScript 中,最好将类型视为具有共同特征的值集合。因为类型只是集合,所以特定值可以同时属于多个集合。
一旦你开始将类型视为集合,某些操作就会变得非常自然。例如,在 C# 中,传递一个既是string
又是int
的值很麻烦,因为没有一个单独的类型来表示这种值。
在 TypeScript 中,一旦你意识到每个类型都只是一个集合,这就会变得非常自然。如何描述一个既属于string
集合又属于number
集合的值?它简单地属于这些集合的并集:string | number
。
TypeScript 提供了许多机制以集合论的方式使用类型,如果你将类型视为集合,你会发现它们更直观。
擦除结构类型
在 TypeScript 中,对象不是单一确切类型的。例如,如果我们构造一个满足接口的对象,我们可以将该对象用于期望该接口的地方,即使这两个之间没有声明关系。
tsTry
interfacePointlike {x : number;y : number;}interfaceNamed {name : string;}functionlogPoint (point :Pointlike ) {console .log ("x = " +point .x + ", y = " +point .y );}functionlogName (x :Named ) {console .log ("Hello, " +x .name );}constobj = {x : 0,y : 0,name : "Origin",};logPoint (obj );logName (obj );
TypeScript 的类型系统是结构化的,而不是名义上的:我们可以使用obj
作为Pointlike
,因为它具有x
和y
属性,它们都是数字。类型之间的关系由它们包含的属性决定,而不是它们是否以某种特定关系声明。
TypeScript 的类型系统也没有具体化:运行时没有任何东西会告诉我们obj
是Pointlike
。事实上,Pointlike
类型在运行时没有任何形式存在。
回到类型作为集合的概念,我们可以认为obj
既是Pointlike
值集合的成员,也是Named
值集合的成员。
结构化类型的后果
OOP 程序员经常对结构化类型的两个特定方面感到惊讶。
空类型
第一个是空类型似乎违背了预期
tsTry
classEmpty {}functionfn (arg :Empty ) {// do something?}// No error, but this isn't an 'Empty' ?fn ({k : 10 });
TypeScript 通过查看提供的参数是否为有效的 Empty
来确定对 fn
的调用是否有效。它通过检查 { k: 10 }
和 class Empty { }
的结构来做到这一点。我们可以看到 { k: 10 }
具有 Empty
所具有的所有属性,因为 Empty
没有属性。因此,这是一个有效的调用!
这可能看起来令人惊讶,但它最终与名义 OOP 语言中强制执行的关系非常相似。子类不能删除其基类的属性,因为这样做会破坏派生类与其基类之间的自然子类型关系。结构化类型系统只是通过描述子类型在具有兼容类型属性方面的关系来隐式地识别这种关系。
相同类型
另一个经常令人惊讶的来源是相同类型
ts
class Car {drive() {// hit the gas}}class Golfer {drive() {// hit the ball far}}// No error?let w: Car = new Golfer();
同样,这不是错误,因为这些类的结构是相同的。虽然这可能看起来像一个潜在的混淆来源,但在实践中,不应该相关的相同类并不常见。
我们将在类章节中了解有关类如何相互关联的更多信息。
反射
OOP 程序员习惯于能够查询任何值的类型,即使是泛型值
csharp
// C#static void LogType<T>() {Console.WriteLine(typeof(T).Name);}
由于 TypeScript 的类型系统是完全擦除的,因此在运行时无法获取有关例如泛型类型参数实例化的信息。
JavaScript 确实有一些有限的原语,例如 typeof
和 instanceof
,但请记住,这些运算符仍然在类型擦除输出代码中存在的实际值上工作。例如,typeof (new Car())
将是 "object"
,而不是 Car
或 "Car"
。
下一步
这是对日常 TypeScript 中使用的语法和工具的简要概述。从这里,您可以