TypeScript 100天 (第3天)





5.00/5 (3投票s)
深入了解类,如何添加我们自己的构造函数,以及如何更改类外部代码是否可以访问我们的字段。
欢迎回到这个系列,我们将从基础到一些相当高级的内容来学习 TypeScript。在第 2 天,我们学习了如何使用 TypeScript 创建一个简单的类。今天,我们将更深入地探讨类,展示如何添加我们自己的构造函数,以及如何更改类外部代码是否可以访问我们的字段。
要求
我不得不承认,我有点厌倦编写简单的加法类,所以我将编写一个功能更丰富的类。今天,我将编写一个 `Point` 类。作为一名专业开发人员,我喜欢了解我的类应该做什么,所以我将为它设定以下需求。
- 该类将允许我添加 `x` 和 `y` 值来表示一个单独的点。
- 该类将为我提供一个 `IsEmpty` 方法,以便我可以确定 `x` 和 `y` 数字是否未设置为 `0,0`。
- 更改 `x` 和 `y` 值的唯一方法是通过 `Offset` 方法。
- 我将能够将一个 `Point` 添加到另一个点以获得一个新点。
- 我可以通过 `IsEqual` 方法确定两个点是否相等(具有相同的坐标)。
- 我将有一个 `ToString` 方法,它会告诉使用该类的用户 x 和 y 的值。
今天练习的代码可以在这里找到。如果您查看代码,您会发现我创建了一个 `tsconfig.json` 文件。这与我在第 2 天和第 1 天创建的文件完全相同,因此您可以复制您之前创建的文件,或使用以下命令添加新文件:
tsc --init
入门
在我的 `day3.ts` 类中,我将首先创建基本的类结构。
class Point {
}
快速题外话
您可能会想为什么我一直谈论面向对象编程,而没有看到任何叫做“对象”的东西。我已经看到过许多复杂的解释,但有一个非常简单的解释。当我们谈论一个对象时,我们谈论的是应用程序在运行时创建的东西;类是对象的模板,所以当我们创建类的实例(创建类的实例是您可能看到的另一个术语)时,我们实际上已经创建了一个对象。我们可以编写包含数千个类的程序;直到我们实际创建它们的实例之前,它们都没有用。
构造函数
当我们创建我们编写的任何类的实例时,您可以将其视为我们正在构造该类。为了帮助我们构造实例,我们有一个特殊的名为构造函数的方法。这个方法特别有趣,因为它有助于我们在开始时使用实例,因此它对类在实例化过程中发生的事情有很大的影响。由于此方法用于构造实例,因此您无法直接从 TypeScript 调用它。唯一直接与之交互的是 `new` 关键字。那么,构造函数是什么样的?
在 TypeScript 中,构造函数使用 `constructor` 关键字,如下所示:
constructor() {
}
提示:如果您的构造函数看起来像这样,您可以删除它。如果您不向类添加构造函数,它会自动为您“添加”一个看起来像这样的构造函数。您不会在代码中看到它,但它就在那里。这就是为什么我在第 2 天没有在我的代码中添加构造函数的原因。
在我的需求中,我说该类将允许我添加 `x` 和 `y` 值来表示该点。为了做到这一点,我将把这些值传递到构造函数中,如下所示:
constructor(x: number, y: number) {
}
有了这个,任何需要创建此类实例的地方,都可以这样创建:
class Point {
constructor(x: number, y: number) {
}
}
const point: Point = new Point(0, 0);
注意:由于我添加了一个带有参数的构造函数,我不再拥有对默认构造函数的访问权限,因此我被迫在这里使用带有参数的构造函数。
我已经传递了值,但实际上并没有对它们做任何事情。任何需要它们的函数都无法访问它们,因为它们只对构造函数可见。我将通过添加几个字段来存储这些值来解决这个问题。我的一个要求是,更改字段值的唯一方法是使用 `Offset` 方法,这表明我不应该从类外部直接访问它们。为了做到这一点,我将引入 `private` 关键字。 `private` 的作用是告诉编译器该字段仅在类内部可见。
您可能会认为我们不能将所有内容都设为 `private`,这是正确的。默认情况下,TypeScript 会将类内部的内容设为 `public`,但您也可以显式地将它们设置为 `public`。
我们的 `private` 字段将看起来像这样:
private x: number;
private y: number;
如果您还记得,在第 2 天,我不得不在创建字段时为其分配一个默认值。当我在构造函数中为它们赋值时,编译器足够聪明,知道我不必分配默认值。我的构造函数现在看起来像这样:
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
即将推出。在未来的帖子中,我将向您展示 TypeScript 的一个便捷技巧,其中我可以在构造函数的签名中声明一个字段。
我现在将解决我的其他要求。让我们从检查一个点是否为空的能力开始。我在这里决定一个空点是 `x` 和 `y` 值都设置为 `0`。
public IsEmpty(): boolean {
return this.x === 0 && this.y === 0;
}
我将要编写的下一个方法检查两个点是否相等。在这种情况下,相等性是指两个点的 `x` 值是否相同,`y` 值是否相同。由于这段代码将在类的实例中运行,我只需要传入我想与之比较的 `Point`。
public IsEqual(point: Point): boolean {
return this.x === point.x && this.y === point.y;
}
通过像这样的小代码增量,我可以快速添加所需的所有功能。偏移坐标的能力如下所示:
public Offset(x: number, y: number) {
this.x = this.x + x;
this.y = this.y + y;
}
重要提示:有一种快捷方式可以将一个值添加到现有值中。如果我在代码中使用 `+=`,我可以更改
this.x = this.x + x;
to
this.x += x;
而不是逐一介绍其他方法,我将在此处添加整个类,以便您可以看到 `ToString` 和 `Add` 方法的样子。到目前为止,您应该已经对这些方法可能的作用有了很好的了解。
class Point {
private x: number;
private y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
public IsEmpty(): boolean {
return this.x === 0 && this.y === 0;
}
public IsEqual(point: Point): boolean {
return this.x === point.x && this.y === point.y;
}
public Add(point: Point): Point {
return new Point(point.x + this.x, point.y + this.y);
}
public ToString(): string {
return 'X is ' + this.x + ' Y is ' + this.y;
}
public Offset(x: number, y: number): void {
this.x += x;
this.y += y;
}
}
如果我想演示这段代码的运行,我可以写这样的内容:
const point: Point = new Point(0, 0);
console.log('Point is empty is ' + point.IsEmpty()); // Should be true
point.Offset(10, 20);
console.log('Point is empty is ' + point.IsEmpty()); // Should be false
const offsetPoint = new Point(10, 20);
console.log('Points are equal is ' + point.IsEqual(offsetPoint)); // Should be true
console.log('The offset is ' + point.Add(offsetPoint).ToString()); // X is 20 and Y is 40
我们已经完成了第 3 天的代码。我承认这里有很多内容需要消化,包括创建自定义构造函数、添加作用域修饰符(`public`/`private`)以及一些替代的加法语法。请浏览 GitHub 上的代码,如果您有任何疑问,请不要害怕提问。在第 4 天,我将向您介绍 TypeScript 中的接口。如果您在 C# 或 Java 等语言中使用过接口,那么 TypeScript 使用它们的方式看起来就像奇迹。