对于习惯于使用其他静态类型语言(例如 C# 和 Java)的程序员来说,TypeScript 是一个流行的选择。
🌐 TypeScript is a popular choice for programmers accustomed to other languages with static typing, such as C# and Java.
TypeScript 的类型系统提供了许多相同的好处,例如更好的代码补全、更早的错误检测以及程序各部分之间更清晰的沟通。虽然 TypeScript 为这些开发者提供了许多熟悉的特性,但值得退一步去看看 JavaScript(因此也包括 TypeScript)与传统的面向对象编程语言有何不同。理解这些差异将帮助你编写更好的 JavaScript 代码,并避免那些直接从 C#/Java 过渡到 TypeScript 的程序员可能会遇到的常见陷阱。
🌐 TypeScript’s type system offers many of the same benefits, such as better code completion, earlier detection of errors, and clearer communication between parts of your program. While TypeScript provides many familiar features for these developers, it’s worth stepping back to see how JavaScript (and therefore TypeScript) differ from traditional OOP languages. Understanding these differences will help you write better JavaScript code, and avoid common pitfalls that programmers who go straight from C#/Java to TypeScript may fall into.
共同学习 JavaScript
🌐 Co-learning JavaScript
如果你已经熟悉 JavaScript,但主要是 Java 或 C# 程序员,这个入门页面可以帮助你解释一些常见的误解和易犯的错误。TypeScript 对类型的某些建模方式与 Java 或 C# 有很大不同,在学习 TypeScript 时牢记这些差异非常重要。
🌐 If you’re familiar with JavaScript already but are primarily a Java or C# programmer, this introductory page can help explain some of the common misconceptions and pitfalls you might be susceptible to. Some of the ways that TypeScript models types are quite different from Java or C#, and it’s important to keep these in mind when learning TypeScript.
如果你是一个刚接触 JavaScript 的 Java 或 C# 程序员,我们建议先学习一些没有类型的 JavaScript,以便理解 JavaScript 的运行时行为。因为 TypeScript 并不会改变你的代码的运行方式,所以你仍然需要学习 JavaScript 的工作原理,才能编写真正有作用的代码!
🌐 If you’re a Java or C# programmer that is new to JavaScript in general, we recommend learning a little bit of JavaScript without types first to understand JavaScript’s runtime behaviors. Because TypeScript doesn’t change how your code runs, you’ll still have to learn how JavaScript works in order to write code that actually does something!
重要的是要记住,TypeScript 使用与 JavaScript 相同的 运行时,因此关于如何实现特定运行时行为(将字符串转换为数字、显示警告、将文件写入磁盘等)的任何资源都同样适用于 TypeScript 程序。不要局限于 TypeScript 特有的资源!
🌐 It’s important to remember that TypeScript uses the same runtime as JavaScript, so any resources about how to accomplish specific runtime behavior (converting a string to a number, displaying an alert, writing a file to disk, etc.) will always apply equally well to TypeScript programs. Don’t limit yourself to TypeScript-specific resources!
反思类
🌐 Rethinking the Class
C# 和 Java 可以说是“强制面向对象”的语言。在这些语言中,类是代码组织的基本单位,也是运行时所有数据和行为的基本容器。将所有功能和数据都放在类中,对于某些问题来说可以是一个很好的字段模型,但并非每个字段都需要以这种方式表示。
🌐 C# and Java are what we might call mandatory OOP languages. In these languages, the class is the basic unit of code organization, and also the basic container of all data and behavior at runtime. Forcing all functionality and data to be held in classes can be a good domain model for some problems, but not every domain needs to be represented this way.
释放函数和数据
🌐 Free Functions and Data
在 JavaScript 中,函数可以存在于任何地方,数据可以自由传递,而不必位于预定义的 class 或 struct 中。这种灵活性极其强大。
“自由”函数(即未关联到类的函数)在没有隐含面向对象层次结构的数据上工作,往往是 JavaScript 中编写程序的首选模式。
🌐 In JavaScript, functions can live anywhere, and data can be passed around freely without being inside a pre-defined class or struct.
This flexibility is extremely powerful.
“Free” functions (those not associated with a class) working over data without an implied OOP hierarchy tend to be the preferred model for writing programs in JavaScript.
静态类
🌐 Static Classes
此外,TypeScript 中不需要来自 C# 和 Java 的某些构造,例如单例和静态类。
🌐 Additionally, certain constructs from C# and Java such as singletons and static classes are unnecessary in TypeScript.
TypeScript 中的 OOP
🌐 OOP in TypeScript
话虽如此,如果你愿意,仍然可以使用类! 有些问题非常适合通过传统的面向对象层次结构来解决,而 TypeScript 对 JavaScript 类的支持会使这些模型更加强大。 TypeScript 支持许多常见模式,例如实现接口、继承和静态方法。
🌐 That said, you can still use classes if you like! Some problems are well-suited to being solved by a traditional OOP hierarchy, and TypeScript’s support for JavaScript classes will make these models even more powerful. TypeScript supports many common patterns such as implementing interfaces, inheritance, and static methods.
我们将在本指南的后面介绍类。
🌐 We’ll cover classes later in this guide.
反思类型
🌐 Rethinking Types
TypeScript 对 类型 的理解实际上与 C# 或 Java 的有很大不同。让我们来探讨一些差异。
🌐 TypeScript’s understanding of a type is actually quite different from C# or Java’s. Let’s explore some differences.
名义具体化的类型系统
🌐 Nominal Reified Type Systems
在 C# 或 Java 中,任意给定的值或对象都有一个确切的类型——要么是 null,一种基本类型,要么是一个已知的类类型。我们可以调用像 value.GetType() 或 value.getClass() 这样的方法在运行时查询确切类型。这个类型的定义会存在于某个类中,有一个名称,并且我们不能用两个具有类似结构的类互相替代,除非它们之间存在明确的继承关系或共同实现的接口。
🌐 In C# or Java, any given value or object has one exact type - either null, a primitive, or a known class type.
We can call methods like value.GetType() or value.getClass() to query the exact type at runtime.
The definition of this type will reside in a class somewhere with some name, and we can’t use two classes with similar shapes in lieu of each other unless there’s an explicit inheritance relationship or commonly-implemented interface.
这些方面描述了一种_具体化的、名义化的_类型系统。我们在代码中编写的类型在运行时是存在的,并且类型之间的关系是通过它们的声明来确定的,而不是通过它们的结构。
🌐 These aspects describe a reified, nominal type system. The types we wrote in the code are present at runtime, and the types are related via their declarations, not their structures.
作为集合的类型
🌐 Types as Sets
在 C# 或 Java 中,考虑运行时类型与其编译时声明之间的一对一对应关系是有意义的。
🌐 In C# or Java, it’s meaningful to think of a one-to-one correspondence between runtime types and their compile-time declarations.
在 TypeScript 中,最好将类型看作一组具有共同特性的值。因为类型只是集合,所以一个特定的值可以同时属于多个集合。
🌐 In TypeScript, it’s better to think of a type as a set of values that share something in common. Because types are just sets, a particular value can belong to many sets at the same time.
一旦你开始将类型看作集合,某些操作就会变得非常自然。 例如,在 C# 中,传递一个值,它可以是 string 或 int,会显得很别扭,因为没有单一的类型可以表示这种值。
🌐 Once you start thinking of types as sets, certain operations become very natural.
For example, in C#, it’s awkward to pass around a value that is either a string or int, because there isn’t a single type that represents this sort of value.
在 TypeScript 中,一旦你意识到每种类型只是一个集合,这就变得非常自然。你如何描述一个值,它要么属于 string 集合,要么属于 number 集合?它只属于这些集合的 联合:string | number。
🌐 In TypeScript, this becomes very natural once you realize that every type is just a set.
How do you describe a value that either belongs in the string set or the number set?
It simply belongs to the union of those sets: string | number.
TypeScript 提供了许多机制来以集合论的方式处理类型,如果你将类型视为集合,你会发现它们更直观。
🌐 TypeScript provides a number of mechanisms to work with types in a set-theoretic way, and you’ll find them more intuitive if you think of types as sets.
擦除的结构类型
🌐 Erased Structural Types
在 TypeScript 中,对象并非只有单一的精确类型。例如,如果我们构造一个满足某个接口的对象,即使该对象与接口之间没有声明性的关系,我们也可以在需要该接口的地方使用这个对象。
🌐 In TypeScript, objects are not of a single exact type. For example, if we construct an object that satisfies an interface, we can use that object where that interface is expected even though there was no declarative relationship between the two.
tsTryinterfacePointlike {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’s type system is structural, not nominal: We can use obj as a Pointlike because it has x and y properties that are both numbers.
The relationships between types are determined by the properties they contain, not whether they were declared with some particular relationship.
TypeScript 的类型系统也不是 具象化的:在运行时没有任何东西会告诉我们 obj 是 Pointlike。
实际上,Pointlike 类型在运行时 以任何形式 都不存在。
🌐 TypeScript’s type system is also not reified: There’s nothing at runtime that will tell us that obj is Pointlike.
In fact, the Pointlike type is not present in any form at runtime.
回到“类型作为集合”的概念,我们可以认为 obj 同时是 Pointlike 值集合和 Named 值集合的成员。
🌐 Going back to the idea of types as sets, we can think of obj as being a member of both the Pointlike set of values and the Named set of values.
结构类型的后果
🌐 Consequences of Structural Typing
OOP 程序员经常对结构类型的两个特定方面感到惊讶。
🌐 OOP programmers are often surprised by two particular aspects of structural typing.
空类型
🌐 Empty Types
第一个是,_空类型_似乎违背了人们的预期:
🌐 The first is that the empty type seems to defy expectation:
tsTryclassEmpty {}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 没有属性。
因此,这是一个有效的调用!
🌐 TypeScript determines if the call to fn here is valid by seeing if the provided argument is a valid Empty.
It does so by examining the structure of { k: 10 } and class Empty { }.
We can see that { k: 10 } has all of the properties that Empty does, because Empty has no properties.
Therefore, this is a valid call!
这可能看起来令人惊讶,但它最终与名义面向对象语言中强制执行的关系非常相似。子类不能 移除 基类的某个属性,因为这样做会破坏派生类与其基类之间的自然子类型关系。结构化类型系统只是通过描述子类型具有兼容类型的属性来隐式地识别这种关系。
🌐 This may seem surprising, but it’s ultimately a very similar relationship to one enforced in nominal OOP languages. A subclass cannot remove a property of its base class, because doing so would destroy the natural subtype relationship between the derived class and its base. Structural type systems simply identify this relationship implicitly by describing subtypes in terms of having properties of compatible types.
相同类型
🌐 Identical Types
另一个经常出人意料的来源是相同的类型:
🌐 Another frequent source of surprise comes with identical types:
tsclass Car {drive() {// hit the gas}}class Golfer {drive() {// hit the ball far}}// No error?let w: Car = new Golfer();
再次说明,这并不是一个错误,因为这些类的 结构 是相同的。虽然这看起来可能会引起混淆,但实际上,不应相关的相同类并不常见。
🌐 Again, this isn’t an error because the structures of these classes are the same. While this may seem like a potential source of confusion, in practice, identical classes that shouldn’t be related are not common.
我们将在“类”一章中详细了解类之间的关系。
🌐 We’ll learn more about how classes relate to each other in the Classes chapter.
反射
🌐 Reflection
OOP 程序员习惯于能够查询任何值的类型,甚至是泛型值:
🌐 OOP programmers are accustomed to being able to query the type of any value, even a generic one:
csharp// C#static void LogType<T>() {Console.WriteLine(typeof(T).Name);}
由于 TypeScript 的类型系统已被完全擦除,因此有关例如泛型类型参数的实例化在运行时不可用。
🌐 Because TypeScript’s type system is fully erased, information about e.g. the instantiation of a generic type parameter is not available at runtime.
JavaScript 确实有一些有限的原语,比如 typeof 和 instanceof,但请记住,这些运算符仍然在类型擦除后的输出代码中对值进行操作。例如,typeof (new Car()) 将是 "object",而不是 Car 或 "Car"。
🌐 JavaScript does have some limited primitives like typeof and instanceof, but remember that these operators are still working on the values as they exist in the type-erased output code.
For example, typeof (new Car()) will be "object", not Car or "Car".
下一步
🌐 Next Steps
这是对日常 TypeScript 中使用的语法和工具的简要概述。从这里,你可以:
🌐 This was a brief overview of the syntax and tools used in everyday TypeScript. From here, you can:
- 阅读完整的手册 从头到尾
- 探索 Playground 示例