新手到高手的 TypeScript 之旅






4.33/5 (3投票s)
在本文中,我们将学习关于 TypeScript 的一切。Angular 本身就是用 TypeScript 编写的,所以如果你想成为 Angular 的高手,那么你也应该了解 TypeScript。
引言
今天我们将深入探讨 TypeScript 的许多概念。如果你还没有阅读我之前的 Angular 系列文章,这里是链接:
继续阅读这篇文章。然后开始探索 TypeScript。让我们开始我们的旅程。
什么是 TypeScript?
TypeScript 不过是 JavaScript 的一个超集,这意味着我们用 JavaScript 能做的一切,也都可以通过 TypeScript 来开发。但 TypeScript 拥有 JavaScript 大多数浏览器不支持的功能。例如:
- TypeScript 具有强类型的概念。正如我们已经知道的,在 C#、Java 等不同的编程语言中,当我们声明任何变量的数据类型时,该类型不能更改,并且如果出现任何类型的异常,我们可以非常容易地调试程序。
同样,在 TypeScript 中,我们可选地声明任何变量的类型,但如果我们声明了特定类型,那么当出现问题时,我们可以非常容易地调试我们的应用程序。
- TypeScript 还带来了很多面向对象编程的特性。我们很久以来一直缺少 JavaScript 中的这些面向对象特性。现在我们有了类、接口、构造函数、访问修饰符、字段、属性、泛型等等概念。
- TypeScript 的另一个优点是,我们可以在编译时而不是运行时捕获错误。但这并不意味着我们可以捕获所有类型的错误,但我们可以捕获很多错误。
因此,它涉及一个编译步骤,当我们编译 TypeScript 代码时,我们可以在部署应用程序之前捕获并解决错误。
- 如果我们使用 TypeScript,那么我们有一些很棒的工具可以在我们的应用程序中使用,例如代码编辑器中的智能感知等。
任何有效且正确的 JavaScript 代码也都是有效的 TypeScript 代码。最重要的一点是,浏览器本身不理解 TypeScript。所以我们需要将我们的 TypeScript 代码编译成 JavaScript。所以这是我们应用程序构建的一部分,每当我们构建应用程序时,TypeScript 编译器就会发挥作用,将我们的 TypeScript 代码编译成 JavaScript 代码,以便浏览器理解。
第一个 TypeScript 程序
在这里,我们将探讨如何安装 TypeScript 以及如何使用 TypeScript 编写第一个程序。现在打开 Node.js 命令提示符(以管理员身份运行)或打开 Visual Studio Code 中的集成终端,然后编写以下命令,这取决于你的选择。
我们已经知道编写命令来安装包的模式。
npm install –g packagename
-g 用于全局安装包。
PS C:\Users\Ami Jan\HelloWorld> npm install -g typescript
这里我们已经安装了最新版本的 TypeScript,即 2.8.3
现在如果你想知道 TypeScript 的编译器版本。那么在 VS Code 终端中输入。
PS C:\Users\Ami Jan\HelloWorld> tsc –version
现在,让我们创建一个 TypeScript 文件并开始编码,但在开始之前,让我们创建一个新文件夹并在其中创建一个新文件。
PS C :\Users\Ami Jan\HelloWorld> cd .\MyFirstAngularProject\ PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject> mkdir ts-helloworld PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject> cd .\ts-helloworld\ PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject\ts-helloworld> code main.ts
VS Code 终端的实际工作方式是 Windows PowerShell 命令在后端运行。
我们看到 “code filename.ext” 用于创建和打开文件,而 “code .” 用于在您执行此命令的目录中打开当前项目。
这里我们创建了 “main.ts” 文件。正如我们已经知道的,任何有效的 JavaScript 都具有有效的 TypeScript。在这个文件中,我们将编写一些纯 JavaScript 代码,所有有效的 JavaScript 代码都具有有效的 TypeScript 代码。
现在我们将编译 TypeScript 代码。所以,首先,确保你的目录中有 TypeScript 文件,并编译它,
TypeScript 代码编译命令
PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject\ts-helloworld> tsc main.ts
如果我们直接使用 “ng serve” 构建 Angular 项目,那么我们无需手动操作,Angular 会自动调用 TypeScript 编译器并在幕后编译代码。
现在让我们打开 “main.js” 文件并运行它,并在终端中显示输出。
PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject\ts-helloworld> code main.js PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject\ts-helloworld> node main.js
看,这与我们在 TypeScript 文件中编写的代码完全相同,但现在它是 JavaScript 文件。所以结果是所有 JavaScript 代码对 TypeScript 代码也有效。
声明变量
让我们讨论 TypeScript 的一些特定功能,这些功能在 JavaScript 中没有。是的,我们正在讨论 TypeScript 强类型。这里我们有两种声明变量的方式。
- 我们可以使用“var”关键字,就像我们在 JavaScript 中一样。
- 我们可以使用“let”关键字。
我们已经知道 JavaScript 有几个不同的版本,我们有:
ES5 (EcmaScript 5):所有浏览器都支持。我们已经使用它很长时间了。
ES6 (2015):它是较新的版本,于 2015 年推出。EcmaScript 团队决定使用年份而不是版本号。这里引入了“let”关键字。
ES2016
ES2017
然后这些新版本都以年份命名。
让我告诉你 var 和 let 的区别,在 “main.ts” 文件中写一些代码。在下面的图片中,我正在终端中一次性编译我的 typescript 代码并运行 javascript 代码。
还有一件重要的事情,在 TypeScript 文件中编写代码后,请务必保存文件,否则您将看到之前编译的代码结果。
看,这是有效的,并显示了数字结果。而且我们已经知道变量在循环外部是不可访问的。
现在用 let 试试,看,即使我还没有保存我的文件,TypeScript 在编译时就知道这是不可能的,你的彩色文件名会变成红色,并提示你有些地方出了问题。
这里 i 超出了作用域。如果你将鼠标悬停在 i 上,你会看到消息说它找不到 i 变量。
让我们保存文件并删除上次构建的 main.js
PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject\ts-helloworld> rm main.js
看,我们已经删除了文件。现在编译 TypeScript 文件,这里我们会在控制台或终端中看到错误。
正如您在上面的图片中看到的,我们生成了带有错误的编译后的 js 文件。即使我们的 TypeScript 文件有错误,TypeScript 编译器也会为我们生成它。让我们看看这个 main.js 中有什么
PS C :\Users\Ami Jan\HelloWorld\MyFirstAngularProject\ts-helloworld> code main.js
正如我们在这里看到的,我们的 TypeScript 文件显示了错误,但是这里的代码是用 var 编译的,实际上 TypeScript 代码默认编译为 ES5,所有浏览器都支持它。而 ES5 没有 let 关键字,这就是为什么它使用 var 关键字。这是一个完全有效的 JavaScript 代码,它在 main.js 中生成,如果我们简单地进入终端并运行这个 main.js 文件,我们会看到与之前相同的结果。
所以结论是 TypeScript 编译器只是在编译时报告这个错误。但它仍然生成有效的 JavaScript 代码。现在我们将在接下来的例子中始终使用 let 关键字,尽管它生成有效的 JavaScript,但至少我们可以在编译时捕获错误。
Types
这里我们将探索 TypeScript 中的不同类型。
让我们从创建整数类型变量开始,然后再次为该变量赋一个字符串。如果我们将鼠标悬停在该变量上,我们将看到错误消息。
但我们可以在 JavaScript 中完美地做到这一点,因为在 JavaScript 中我们可以动态更改变量的类型,但在 TypeScript 中,我们会得到编译错误。现在再一次,我们也可以使用 TypeScript 编译器编译它,并且我们肯定会像以前一样得到有效的 JavaScript 代码。但是,尽管它会生成有效的 JavaScript,但该代码将来可能会中断,因为我们可能会在应用程序中的其他地方(例如循环中)使用此 countNumber 变量。因此,我们的程序将在运行时中断。
如果我们在没有初始化的情况下声明变量会怎样?让我们看看这种类型。
我们可以看到这里我们有一个变量类型是 any,就像我们在 JavaScript 中有一个变量一样。所以这里我们可以做一些技巧。
let a; a = 5; a = true; a = "usama";
看,一切都在发生,甚至编译器都没有报告它,因为它在声明时不知道类型。
解决这类问题的方法是使用类型注解,我们在第一条语句中声明变量的类型,然后如果尝试初始化不同类型的值,就会看到编译时错误。
在 TypeScript 中,我们有不同种类的类型。
let a: number; // numeric and floating point numbers
let b: boolean; // true or false
let c: string; // string datatype
let d: any; // We’ve already experienced with it before.
let e: number[]; // array
现在假设我们想在声明时也初始化变量。
let isAvailable: Boolean = true;
let marks: number = 530;
TypeScript 也使用模板字符串。模板字符串用反引号 (``) 包裹,我们使用 ${} 语法,就像我们在 C# 中(字符串插值)一样。
let fullName: string = `usama`;
let position: number = 1;
let sentence: string = `Hello, World! My name is ${fullName} and I’ve got ${position + 1} in exams`;
我们可以定义数组的维度,但它是可选的。
let f: number[] = number[1, 2, 3];
let g: any[] = any[1, true, ‘a’, false];
当然,这些技术不推荐,但我们在这里讨论它们只是为了了解这些事情。
正如我们在 C# 中有泛型一样,TypeScript 中也有泛型。我们也可以使用泛型声明数组。
let list: Array<number> = [1, 2, 3];
我们还有 TypeScript 中的枚举,枚举用于定义常量。我们将所有常量放入一个容器中。在旧的 JavaScript 语言中,我们明确定义常量,例如:
const ColorRed = 0;
const ColorGreen = 1;
const ColorBlue = 2;
但现在在 TypeScript 中我们可以这样定义枚举。
enum 枚举名称 { 元素 }
enum Color { Red, Green, Blue }
看,我们在 TypeScript 中看到了智能感知,它有助于快速编写代码。我们不需要记住任何东西。我们已经知道枚举默认是基于 0 的,每个下一个值都会递增。所以我们不需要在枚举中设置显式值。但最佳实践是显式地为元素赋值。因为如果你在应用程序中使用旧的枚举,然后将来你在枚举元素中添加一个元素,那么元素的顺序和它们的值将改变。所以最好的方法是分配显式值,并为枚举的第一个元素分配 1 值,因为如果你的枚举是基于 0 的,并且你正在使用数据库,那么你的应用程序可能会遇到错误。
enum Color { Red = 1, Green = 2, Purple = 4, Blue = 3 }
现在让我们编译我们的 TypeScript 文件,看看我们的 TypeScript 枚举是如何渲染成 JavaScript 的。
PS (Take Care of File Folder Path) > tsc main.ts
你可以看到我们通过 TypeScript 代码编译的 JavaScript 代码有多么复杂。这就是为什么开发人员喜欢 Angular 和 TypeScript。我们只写一行语句,在 JavaScript 中却编译成多行。这是 TypeScript 和 JavaScript 之间的一个主要区别。
如果我们想用任何枚举元素初始化变量。那么我们将声明变量为:
enum Color { Red, Green, Blue } let c: Color = Color.Green; Destructuring
ECMAScript 2015 包含在 TypeScript 中的另一个漂亮的特性是解构。让我与你分享一个例子。
let x = [50, 100];
let [e1, e2] = x;
console.log(e1); // 50
console.log(e2); // 100
在底层,它创建了两个不同的变量 e1 和 e2,分别包含 x 数组的第一个和第二个索引的值。
展开运算符
如果你是 JavaScript 专家,你肯定会了解 JavaScript 的这些特性。是的,JavaScript 中有一个 spread 的概念。这里我们将在 TypeScript 中实现 spread。实际上它与解构相反,让我和你分享一个例子。
let x = [5, 7];
let y = [3, 8];
let both = [9, x, y, 10];
看,如果我们像这样组合数组,
console.log(both); // [9, [5, 7], [3, 8], 10]
它将以这种格式显示。但是如果我们使用扩展运算符,
let both = [9, …x, …y, 10];
它将按原样渲染我们的数组,
console.log(both); // [9, 5, 7, 3, 8, 10]
你可以看到这三个点与变量名(…x)一起被称为展开。在解构中,我们正在拆分事物,它允许我们将一个数组展开到另一个数组中。我们甚至可以将一个对象展开到另一个对象中。
类型断言
让我们从声明变量开始。
let text = ‘usama’; // Here Typescript know that ‘text’ is string.
这里智能感知指导我们如何编写代码。但有时 TypeScript 会对变量的类型感到困惑,例如当变量声明为“any”时。
let text; // any
text = ‘usama’;
let found = text; // Here you’ll not see the intellisense.
正如我们所看到的,我们没有得到任何智能感知。我们无法应用之前实现的任何方法。因此,在这种情况下,我们需要手动告诉 TypeScript 编译器变量的类型。这就是类型断言。
有两种方法可以进行类型断言。
(<类型>变量)。
在这里我们获得了所有可以在字符串类型上实现的方法和函数。
第二种方法是使用 “as” 关键字,就像我们在 C# 中一样。
(文本 as 字符串)。
请记住,类型断言不会在运行时更改变量的类型。实际上,它不会在内存中重构变量。它只是帮助我们让变量像特定类型一样行为。
箭头函数
这也是一个重要的概念,在 JavaScript 中我们已经知道如何声明函数
let log = function (text) {
console.log(text);
}
log(‘usama’);
我们这里不需要任何函数关键字。这种技术与 C# 中类似。在 C# 中,我们称之为 Lambda 表达式,而在 TypeScript 中,我们称之为箭头函数。是的,它们完全相同。
如果方法体只有一条语句,那么我们也可以去掉花括号,如果只有一个参数,那么我们也可以去掉参数的括号。如果有多行,那么我们使用花括号,如果参数有多个,那么我们必须使用带括号的参数。
还有一件事,这里字符串总是用双引号。否则你会看到错误。
let logMessage = message => console.log(message);
let logMessage1 = (message, recipient) => {
console.log(“Hello” + message);
console.log(“I\’m here” + recipient);
}
logMessage(“usama”);
logMessage1(“sweety”, “Usama”);
但最佳实践是始终将参数放在括号中,以使其更具可读性。
如果我们的函数没有任何参数,那么我们肯定会把括号留空,然后编写我们的箭头函数。
let logMessage2 = () => console.log(“Hello, World!”);
如果我们编译我们的箭头函数,让我们看看它在 JavaScript 中是什么样子。但要保存 TypeScript 文件。
接口
让我们看看如何在 TypeScript 中使用自定义类型。假设我们正在使用一个需要 2 个点来绘制东西的函数。
let drawPoints = (x, y) => {
// to do something
}
但是如果有很多点,并且我们将所有参数都传递给一个函数,那么这会是一种代码异味。我们总是努力使代码清晰。
let drawPoints = (a, b, c, d, e, f, g, h) => {
// ….
}
与其传递所有参数,不如传递一个包含所有这些属性的单个对象。
let drawPoints = (point) => {
// here point is the object
}
然后我们可以这样调用这个函数。
drawPoints({
x: 5,
y: 8,
z: 9 // so on how much you need
});
现在我们的代码清晰多了,但这种逻辑仍然存在问题。我们可以将任何东西传递给点对象,例如:
drawPoints({
name: “Usama”,
loves: “Azure”
});
如果我们已经在箭头函数中定义了点的实现,并且我们传递了不同名称的参数,那么我们的代码肯定会在运行时中断。因此,需要限制用户传递特定名称的参数。
这里我们有两种解决方案来解决这个问题。
使用内联注解
let drawPoints = (point: { x: number, y: number }) => { }
对于简单的情况,它工作得很好,但假设我们正在进行函数重载,那么我们还需要与其他函数重复内联注解。所以在这种情况下,我们需要使用 'Interface'。
概念在每种编程语言中都是相同的,只是语法不同。如果你了解接口的概念,因为我们在 C#、Java 等面向对象语言中使用接口,那么你肯定会明白为什么接口很重要。
进入 TypeScript 文件并定义接口。定义接口就像定义对象的形状。
interface Point {
x: number,
y: number
}
现在简化函数的实现。
let drawPoints = (point: Point) => {
}
现在它更清晰了,我们可以在多个地方重用它。如果你正在定义自定义类型,请始终使用 Pascal 命名法,就像我们在代码中做的那样。它在开发过程中对你有所帮助。
这是我的最终 main.ts 文件。
我的 main.js 文件是我通过 main.ts 文件编译的。
在某些编程语言中,我们总是需要明确定义类和接口来实现接口,例如在静态编程语言中。但正如我们所知,JavaScript 本身和 JavaScript 家族是一种动态编程语言。因此,在这里我们不需要明确地实现接口。
interface Label {
label: string;
}
function printMessage(labelObj: Label) {
console.log(labelObj.label);
}
let myObj = { name: "Usama", label: "Hello, World!"};
printMessage(myObj);
有关 TypeScript 接口在不同场景下的更多示例,请打开此 链接。如果你了解 C# 或 Java 中的 OOP,你会发现这完全是你已经知道的。只是这里的语法有所变化。没有什么新鲜事。
类与对象
现在我们已经看到了如何定义对象的形状,但这种实现仍然存在问题。在面向对象编程中,我们有内聚性的概念,这意味着相关的事物应该是一个单元的一部分。块中的所有内容都应该与该块相关。事物是完全结构化且相互关联的。这称为内聚性。
现在看前面的例子,我们创建了接口并在函数中实现了它。在这里我们违反了内聚的概念。尽管我们可以以这种方式使用接口,但我并不是告诉你应该避免使用接口而选择类。我只是在创建场景来让你理解不同的概念。接口有它自己的好处,没有接口我们永远无法享受到。所以接口也很重要。假设你在你的项目中使用一个实用程序库,那么你将如何在函数中使用对象。当然是下面的代码技术。所以了解它也很重要。
interface Point {
x: number,
y: number
}
let drawPoints = (point: Point) => {
}
这里,点应该与函数高度相关,它们不应该是两个独立的事物。
所以 类 是
“高度相关的属性和方法。”
是的,我知道这是我自己的定义,如果这篇文章的任何读者是经验丰富的,他肯定会杀了我。但这只是为了让你们理解。这并不是类的正确定义。
在上面的例子中,我们不能将函数移到接口内部,因为接口纯粹是用于声明的,它们不能有任何实现。但是我们也可以在接口中声明函数原型。
interface Point {
x: number,
y: number,
draw: () => void // no parameters return void
}
由于这些属性和方法是同一个代码块的一部分,因此我们不需要在方法内部传递参数。这就是 draw 方法没有参数的原因。现在我们可以在其他地方实现它。
我们正在讨论内聚原则,我们已经知道耦合和内聚原则应用于类。所以,我们将其转换为类,并用一个新的例子遵循类的语法。
class Vehicle {
x: number;
y: number;
Drive() {
// ….
}
CoveredDistance(distance: Vehicle) {
// …..
}
}
这里我们可以看到事物是完全相互关联的。当一个函数是类的一部分时,它被称为方法。
现在让我们创建这个自定义类型的对象变量。
let car: Vehicle;
看,我们得到了可用方法和属性的智能感知。
现在让我们用这个对象变量运行 Drive 方法。编译并运行程序,但在此之前务必保存您的 TypeScript 文件。
这里我们遇到了编译时错误。
看,它是未定义的。实际上在上面的代码中,我们只是创建了特定类型 'Vehicle' 变量。它实际上并没有在内存中分配空间。所以,我们需要让它完整。
let car: Vehicle = new Vehicle();
正如我们所看到的,我们重复了两次 Vehicle,所以我们需要让代码更简洁。
let car = new Vehicle();
现在可以了。这里 TypeScript 编译器通过初始化自动知道 ‘car’ 是 Vehicle 类型。
现在我们来编译、运行 main.js 并检查 'car' 的类型。
现在看,我们甚至没有为属性赋值,它仍然成功编译。如果我们 console.log Drive() 中的属性
现在让我们初始化 x 和 y 点。现在它成功运行了。
请记住,Vehicle 是类,car 是对象。而对象是类的实例。
构造函数
看这里我们有三行代码来初始化属性。这种方法不好。构造函数主要用于初始化属性。想象一下,如果类有更多的属性,那么你肯定会厌倦逐行初始化所有属性。
let car = new Vehicle();
car.x = 50;
car.y = 100;
让我们讨论一下构造函数的概念。每个类都有一个默认构造函数,它基本上在我们通过 new Obj() 创建类的实例时被调用。
但是假设我们不知道构造函数参数及其模式。在 C#、Java 等其他编程语言中,我们也有默认构造函数,但在 TypeScript 中,我们没有默认构造函数。所以如果我们不提供初始值,我们将得到错误。
let car = new Vehicle();
car.Drive();
所以在这种情况下,我们需要将参数设为可选。我们可以通过使用问号 (?) 来实现。
在 C# 中我们可以有多个构造函数,但在 TypeScript 中我们不能有多个构造函数。
这就是我们使其可选的原因。
现在我们没有收到任何编译错误。
继承
在 TypeScript 中,我们还可以通过子类继承基类来扩展任何类的实现。是的,我们可以在 TypeScript 中做到这一点,这就是 JavaScript 的美妙之处。
class Vehicle {
move(direction: string = “forward”) {
console.log(`Vehicle is moving ${direction}`)
}
}
class Car extends Vehicle {
numberOfSeats(number: int) {
console.log(`Car has ${number} of seats`);
}
}
let car = new Car();
car.numberOfSeats(4);
car.move(“forward”);
构造函数继承
是的,我们也可以在构造函数中进行继承,就像我们在 C# 中使用 base() 一样,这里我们使用 super()。
class Vehicle {
private wheelers: number;
constructor(wheel: number) {
this.wheelers = wheel;
console.log(`Vehicle has ${this.wheelers} wheels`);
}
move(direction: string = 'forward') {
console.log(`Vehicle is moving ${direction}`)
}
}
class Car extends Vehicle {
constructor(wheels: number) { super(wheels); }
numberOfSeats(number: number) {
console.log(`Car has ${number} of seats`);
}
move(direction: string) {
super.move(direction);
}
}
class MotorBike extends Vehicle {
constructor(wheels: number) { super(wheels); }
numberOfSeats(number: number) {
console.log(`MotorBike has ${number} of seats`);
}
move(direction: string) {
super.move(direction);
}
}
let car = new Car(4);
let motorbike : Vehicle = new MotorBike(2);
car.move('backward');
motorbike.move('forward');
我们已经知道,如果变量是基类型,那么我们可以用这个变量创建子类型对象。这是面向对象编程的概念。不要混淆结果,这些实际上是简单类的简单对象。这是结果。
访问修饰符
看上面的例子,创建对象后我们仍然可以初始化变量。
let car = new Vehicle(50, 100);
car.x = 30;
car.y = 70;
在我们的场景中没问题,但我们想限制我们的属性不能在类外部访问,这里我们需要访问修饰符。它减少了出现 bug 的几率。
我们在类的成员上应用访问修饰符,以控制类成员的可访问性。
在 TypeScript 中,我们有 3 种访问修饰符。
- Public
- Private
- Protected
默认情况下,类的所有成员都是公共的,这就是为什么我们可以通过实例对象访问 x 和 y。让我们将属性设为私有,如果我们使用实例对象访问 x 和 y,那么我们将看到编译错误。
class Vehicle {
private x: number;
private y: number;
constructor(x?: number, y?: number) {
this.x = x;
this.y = y;
}
Drive() {
console.log('Initial Point is: ' + this.x +
' And Final Point is: ' + this.y);
}
}
let car = new Vehicle(50, 100);
car.Drive();
现在我们来编译代码并打开 main.js。
现在让我们讨论 TypeScript 中一个非常棒的功能。如你所见,我们在构造函数中重复使用 (this.x, this.y) 使得代码冗余。我们可以通过简单地在构造函数头部添加 private 前缀并删除冗余代码来使代码更简洁。
class Vehicle {
constructor(private x?: number, private y?: number) {
}
Drive() {
console.log('Initial Point is: ' + this.x +
' And Final Point is: ' + this.y);
}
}
let car = new Vehicle(50, 100);
car.Drive();
它会自动创建私有类成员属性并像我们之前在构造函数中那样初始化它。现在让我们通过生成它的 main.js 来验证它。
是的,看起来一样。所以 TypeScript 在构造函数方面有一个非常漂亮的功能。我们也可以将构造函数参数设为 public,这一切都取决于我们的需求。
我们已经知道 protected 成员只在派生类中可用。所以让我们做个实验。
class Vehicle {
protected wheel: number;
constructor(wheel: number) {
this.wheel = wheel;
console.log(`Vehicle has ${this.wheel} wheels`);
}
}
class Car extends Vehicle {
private name: string;
constructor(model: string, wheels: number) {
super(wheels);
this.name = model;
console.log(`My Car is ${this.name} and it has ${this.wheel}`);
}
}
let car = new Car("Honda", 4);
看,我们可以看到 this.wheelers 可以在派生类中访问。
this 指针
你知道 this 指针的概念吗?this 指针包含其类型的引用。看在派生类中,我们可以使用父成员的变量,因为该成员在父类中是受保护的,并且受保护的成员在派生类中是可访问的。在这里我们使用 this,这意味着 Car 类具有 wheel 字段。
属性
现在这种实现仍然有一个小问题。我们有构造函数来初始化字段。我们有函数来执行一些自定义操作,这个操作可以是任何东西。但我们没有任何类型来读取字段的值。
我们有一些方法可以返回字段的值,例如:
第一种技术是,
class Vehicle {
constructor(private x?: number, private y?: number) {
}
Drive() {
console.log('Initial Point is: ' + this.x +
' And Final Point is: ' + this.y);
}
getX() {
return this.x;
}
}
let car = new Vehicle(50, 100);
console.log(car.getX());
car.Drive();
这段代码的结果将是。
现在,如果我们想在创建对象后,甚至在初始化变量后调用其构造函数,然后设置字段值。那么我们就使用 setter 技术。这些都是我们作为 C# 开发者所做和所了解的事情。所以让我们在类中添加 Setter。
class Vehicle {
constructor(private x?: number, private y?: number) {
}
Drive() {
console.log('Initial Point is: ' + this.x +
' And Final Point is: ' + this.y);
}
getX() {
return this.x;
}
setX(value) {
if (value < 0) {
throw new Error('Enter a valid value');
}
this.x = value;
}
}
let car = new Vehicle(50, 100);
car.setX(70);
console.log(car.getX());
现在我们也可以在类外部设置字段值。我们也在 C# 中做同样的事情,最初我们手动创建 getter 和 setter,但后来 C# 引入了属性的概念。
这里我们手动编写了 getter setter 代码。同样,现在 TypeScript 也引入了一些新功能,现在我们有了 get 和 set 内置关键字。看下面的代码。
class Vehicle {
constructor(private x?: number, private y?: number) {
}
Drive() {
console.log('Initial Point is: ' + this.x +
' And Final Point is: ' + this.y);
}
get X() {
return this.x;
}
set X(value) {
if (value < 0) {
throw new Error('Enter a valid value');
}
this.x = value;
}
}
let car = new Vehicle(50, 100);
car.X = 80;
console.log(car.X);
如果你是经验丰富的开发人员,你肯定知道如果想让属性只读,那么我们只需使用 getter,而不需要实现或编写 setter 的代码。所以注释掉我们类中的 setter 代码,使属性只读。
看这里我们在初始化只读属性时得到了错误。
模块
现在我简化了我的 Vehicle 类,现在我只有一个简单的构造函数和一个方法。这是一个非常简单的程序。
class Vehicle { constructor(private x?: number, private y?: number) { } Drive() { console.log('Initial Point is: ' + this.x + ' And Final Point is: ' + this.y); } } let car = new Vehicle(50, 100); car.Drive();
只有 1 个文件,但如果是在现实世界中,应用程序由数百个文件组成。我们不会把所有代码都写在一个文件中。所以这里我们将学习如何在文件中弃用代码。
首先我们将 Vehicle 类的定义移到一个文件 (Vehicle.ts) 中。
PS > code Vehicle.ts
现在将 Vehicle 类从 main.ts 移到 Vehicle.ts。这就是我们所说的模块概念。在 TypeScript 中,我们把每个文件都看作一个 Module。所以在这个程序中,我们可以说我们有两个模块,但不完全准确。在 Vehicle.ts 文件中,我们无法在文件外部访问 Vehicle 类。这个文件有自己的作用域。看这里我们得到了错误。
但是,如果我们要在这个文件之外访问这个类,我们需要导出这个类。只需在类前面加上 export 关键字即可。
export class Vehicle {
constructor(private x?: number, private y?: number) {
}
Drive() {
console.log('Initial Point is: ' + this.x +
' And Final Point is: ' + this.y);
}
}
现在这个文件被视为一个模块,保存导出文件。现在我们还需要在 main.ts 中导入这个文件。所以,
import { comma separated class names if multiple (class name) } from ‘relative path (name of module)’;
import { Vehicle } from './Vehicle'; let car = new Vehicle(50, 100); car.Drive();
(./) 是因为这两个文件 (main.ts 和 Vehicle.ts) 都在同一个文件夹中。不要在文件名中添加扩展名,否则你会看到编译错误。
关于模块化,还有很多内容可以讨论,但我们讨论过的这些内容足以开始 Angular 项目。
请记住,Angular 模块的定义方式有所不同,在 Angular 模块的情况下,我们不添加相对路径。当我们定义 Angular 模块并将其导入到我们的 TypeScript 文件中时,我们只使用库名称作为模块名称,例如:
import { Vehicle } from ‘@angular/core’;
在 TypeScript 中,我们将程序划分为多个文件。在每个文件中,我们导出一个或多个类型,这些类型可以是类、函数、简单变量和对象,而我们需要使用这些类型的地方,我们首先需要导入它们。当文件顶部有 import 和 export 语句时。该文件在 TypeScript 的角度来看就是一个模块。我们在 Angular 中也有模块的概念,但这有点不同,它们不是关于代码在不同文件中的组织。它们是关于将应用程序组织成更小的功能区域。
结论
在这里我们已经涵盖了 TypeScript 的很多内容。但并非所有内容都完整,但如果你已经阅读了本文,那么你可以继续项目。如果你想了解更多关于 TypeScript 的信息。那么最好的来源就是它的 文档。
微软最近在 TypeScript 中引入了新的更新。如果你想了解这些更新,这里是 链接。