和我一起学习 - TypeScript:类型研究






4.75/5 (6投票s)
详细讨论 TypeScript 的类型系统。
其他部分
引言
近几年来,随着技术的飞速发展,用户对具有丰富用户体验的 Web 应用程序的需求不断增长。为了满足这一需求,JavaScript 开发呈指数级增长。坦率地说,JavaScript 最初并不是为支持当今大规模复杂开发而设计的。因此,它带来了昂贵的维护成本。TypeScript,一个由微软发起的开源语言项目,旨在将 JavaScript 推向新的水平并克服这些问题。TypeScript 的一个主要特点是它是一种强类型语言。在本文中,我们将讨论 TypeScript 的类型系统。
背景
JavaScript 最初被创建为一种脚本语言,但它仍然可以灵活地采用面向对象编程。TypeScript 通过引入一些面向对象概念,提供了更便捷的方式来访问这些功能。
- 静态类型
- 类
- 接口
- Generics
- 模块
TypeScript 是一种静态类型编译语言,在 JavaScript 之上添加了一个静态类型层。编译时,编译器解析 TypeScript 并输出纯 JavaScript。作为一种解释型语言,JavaScript 的调试只能在运行时进行。但是,编译允许 TypeScript 在部署代码之前引发错误。
由于 TypeScript 是 JavaScript 的超集,任何有效的 JavaScript 代码也都是有效的 TypeScript 代码。换句话说,所有 JavaScript 代码都是 TypeScript 代码,但 TypeScript 代码不一定是 JavaScript。因此,现有的 JavaScript 应用程序可以轻松地转换为 TypeScript 应用程序,而不会遇到太多麻烦。
工具和 IDE
作为微软的一项举措,TypeScript 作为内置功能包含在 Visual Studio 2015 中。但对于不使用 Visual Studio 的开发者来说,有一个免费提供的 npm 模块。对于不熟悉这个术语的人来说,npm 是一个包管理器。您可以在 https://npmjs.net.cn 上了解更多信息。要使用 npm,您需要在计算机上安装 Node.js。如果没有,请到 https://node.org.cn 获取。
安装 Node.js 后,打开 Node.js 命令提示符 并输入命令
npm install -g typescript
如果安装成功,恭喜您!您现在将能够将 TypeScript 文件编译为 JavaScript 文件。
除了 Visual Studio,一些其他流行的文本编辑器也提供了 TypeScript 插件。我正在使用 Atom (https://atom.io) 和 atom-typescript 插件 (https://atom.io/packages/atom-typescript)。
类型系统基础
顾名思义,TypeScript 为 JavaScript 变量引入了可选类型,使其成为一种强类型编程语言。TypeScript 的基本类型有:
未定义
null
void
布尔值
number
字符串
所有类型都是 any
超类型的子类型。TypeScript 类型还包括 array<font color="#111111" face="Segoe UI, Arial, sans-serif"><span style="font-size: 14px;">,</span></font>
、enum
。使用强类型是提高代码质量和可读性的成熟技术。它也有助于编译器在编译时捕获错误,而不是在运行时破坏应用程序。
变量推断
为了以最小的努力提供类型安全,TypeScript 会尽可能进行推断。一旦推断完成,变量就成为推断类型的强类型变量。因此,您将不允许对变量执行推断类型不允许的操作。
在您的工作文件夹中,创建一个名为 types.ts 的新文件。将以下行放入文件中并保存。
//Inference
var test = 0;
现在是时候编译它了。打开 Node.js 命令提示符 ,导航到您的项目文件夹,然后告诉 TypeScript 编译器 编译 types.ts 文件。
tsc types.ts
将创建一个与 TypeScript 文件同名的新 JavaScript 文件,位于同一目录下。打开 .js 文件,它应该包含 .ts 文件的相同内容。
现在,将以下行追加到 types.ts 文件并进行编译。
test = "I am a string";
编译器将提供一个错误消息,说 “无法将 'string' 转换为 'number'。”
嗯,TypeScript 终于露出了它的真面目。在 JavaScript 中可以正常执行的代码,在 TypeScript 中却无法编译。第一行,test
被推断为 number
类型变量。当我们试图将 string
赋值给 number
类型变量时,它别无选择,只能抱怨。
可选静态类型注解
有时,由于业务流程的性质,无法进行变量推断。但即使那样,在 TypeScript 中,我们仍然可以通过 可选静态类型注解 来定义变量的类型。一旦为变量声明了静态类型注解,就不能为其分配任何其他类型的值。
打开 types.ts 文件,注释掉导致问题的行。将以下代码行添加到 types.ts 文件的末尾,保存并在 Node.js 命令提示符中,输入命令进行编译。
//...
var num: number;
var str: string;
num = 0;
str = "Hello World!";
编译完成后,打开 .js 文件看一看。它是我们非常熟悉的 JavaScript 语法,没有任何“类型注解”。现在,在最后添加下面加粗的代码片段并再次编译。
//...
var num: number;
var str: string;
if(1 != 2)
{
num = "This won't compile"; //Cannot convert 'string' to 'number'
}
num = 0;
str = "Hello World!";
啊,编译错误……很酷,对吧!
一些有趣的类型
Any 类型
当一个变量在没有 推断 或没有任何 类型注解 的情况下声明时,该变量将被声明为 any
类型。Any 类型变量与任何标准 JavaScript 变量的行为相同,并且可以赋值给任何值。Any 类型为我们提供了一个强大的平台来将现有的 JavaScript 代码转换为 TypeScript。
为了了解 any 类型,请注释掉上一个代码片段中的 if
块,然后在 types.ts 文件中输入以下行。编译它。
//Any
var anything: any; //explicit any
var implicit_anything; //implicit any
anything = -1;
anything = [1, 2, 3]; //No problem
anything = "This will be compiled";
implicit_anything = -1;
implicit_anything = [1, 2, 3]; //No problem
implicit_anything = "This will be compiled too";
数组类型
TypeScript 允许我们通过两种方式声明数组:使用方括号声明和声明为泛型 Array
类型。
//Array
var arr1: number[] = [1, 2, 3];
var arr2: Array<number> = [1, 2, 3];
枚举类型
Enum
s 是在 TypeScript 中命名数字的好方法,就像在任何其他语言中一样。要使用 enum
//Enum
enum MyEnum1 {Val1, Val2, Val3};
var enumVal1: MyEnum1 = MyEnum1.Val1;
enum MyEnum2 {Val1 = 11, Val2, Val3};
var enumVal2: MyEnum2 = MyEnum2.Val2;
默认情况下,如果未另行声明,enum
的起始值为零。在示例中,赋值给 enumVal2
的值是 0
。但是 enumVal2
被赋值为 12
。
Void 类型
如果我们从函数中返回任何内容,则该函数被认为返回 void
类型。换句话说,void
意味着“无”。在 TypeScript 世界中,不存在 void 类型的任何值。我们可以为 void
类型使用类型注解,但这没有任何影响。但与其他类型一样,一旦我们将变量的类型注解为 void
,就不能为其分配其他类型的值。
为了开始使用 void
类型,让我们在 types.js 文件中添加以下行并进行编译。
//Void
var returnVal: void
returnVal = 1;
正如我所说,“无法将 'number' 类型分配给 'void' 类型。”如果我们删除最后一行,它将成功编译。
联合类型
TypeScript 也允许我们使用 Union
类型。联合类型变量可以存储声明的多种类型之一。如果我们声明一个联合类型变量来包含数字、string
或字符串数组,它将包含这些类型中的任何值。但是,如果我们尝试分配其他类型的值,例如布尔值,它将抛出编译错误。让我们试试。在 types.ts 文件中输入以下行。
//Union Type
var unionVariable: number | string | string[];
unionVariable = 2;
unionVariable = 'Hello World';
unionVariable = ["Hello", "World"];
unionVariable = true; //Error
保存并编译。“无法将 'boolean' 类型分配给 'number | string | string[]' 类型。”
现在删除最后一行并尝试。它将成功编译,并创建 .js 文件。
匿名类型
变量可以声明为匿名类型变量。将代码添加到 types.ts 文件的末尾。取消注释掉已注释掉的行并进行编译。
var v: {id: number, value: string};
//v = {value: 1, id: "A"} //Error
v = {value: "A", id: 1};
如果尝试将不匹配我们声明为匿名类型的结构的匿名类型变量分配给某个值,编译器将提供错误。
高级类型主题
既然我们已经熟悉了 TypeScript 中的类型,我们可以进入下一级别——关于类型系统的一些高级主题。
类型别名
有了这项功能,我们可以为变量定义别名并将其作为替代使用。如果您想使用 union
类型,它会非常方便。与其列出变量可能支持的一堆类型,不如现在定义一个别名。让我们为我们之前声明的 union
定义一个别名,并尝试执行相同的操作。
//Type Alias
type myUnion = number | string | string[];
var myUnionVariable: myUnion;
myUnionVariable = 2;
myUnionVariable = 'Hello World';
myUnionVariable = ["Hello", "World"];
//myUnionVariable = true; //Error
作用域
TypeScript 允许我们为变量和常量设置作用域。在 TypeScript 中,我们可以使用 var
或 let
来声明变量。const
用于创建常量。所有这些都可以通过在任何代码块(用花括号括起来)外部声明来作用域到全局级别。这样做后,可以从任何地方访问它们。除此之外,
var
声明作用域局限于函数的局部,这意味着如果一个 var
在函数内的任何地方声明,它可以在该函数内的任何地方使用。
let
用于声明作用域限制为最近闭包的变量。这使我们能够将变量作用域限制到任何由花括号括起来的块,并使其在块外部不可访问。
使用 const
声明的常量具有与使用 let
声明的变量相同的范围行为。它们可以用于全局、局部或块级别。既然我们说到这里,类型别名也具有相同的行为。
为了玩转作用域,让我们打开 types.ts 文件并包含以下行。
var globalVar: number = 0;
let globalLet: number = 0;
const globalConst : number = 0;
function myFunction (){
var localVar: number = 1;
let localLet: number = 1;
const localConst : number = 1;
{
var blockVar: number = 1;
let blockLet: number = 1;
const blockConst : number = 1;
type blockTypeAlias = number | string | string[];
}
globalVar = 2;
globalLet = 2;
alert(globalConst);
localVar = 2;
localLet = 2;
alert(localConst);
blockVar = 2;
//blockLet = 2; //Error
//alert(blockConst); //Error
//var blockTypeVar: blockTypeAlias; //Error
}
globalVar = 3;
globalLet = 3;
alert(globalConst);
//localVar = 3; //Error
//localLet = 3; //Error
//alert(localConst);
//blockVar = 3; //Error
//blockLet = 3; //Error
//alert(blockConst); //Error
随机取消注释掉已注释掉的行并尝试编译。
类型转换
让我们从以下代码片段开始。
type type1 = {id: number, name: string}
type type2 = {name: string, id: number}
var v1: type1 = {id: 2, name: 'abc'}
//var num = <number> v1; //Error
var v2 = <type2> v1;
//v2 = 2; //Error
v2 = <any> v2;
var v3 = <any> v2;
v3 = 2;
看起来 TypeScript 变量可以强制转换为超类型或具有相似属性的其他类型。现在,取消注释掉已注释掉的行并编译。您将收到错误。对于这种编译时验证,TypeScript 的强制转换被称为 “类型断言” 而不是类型转换。
类型守卫
TypeScript 允许我们通过 any
和联合类型变量在单个变量中分配多种类型的值。它还允许我们在条件块中使用 JavaScript 的 typeof
和 instanceof
运算符来执行针对特定类型的语句。这种技术被称为 类型守卫。
var a: any;
if(typeof a === "number")
{
//a.toLowerCase(); //Error
}
if(typeof a === "string")
{
a.toLowerCase(); //No Problem
}
a.toLowerCase();
type t = number | string;
var b: t;
if(typeof b === 'string')
{
b.toLowerCase();
}
//b.toLowerCase(); //Error
将代码片段添加到我们著名的 types.ts 文件中,并通过取消注释掉已注释掉的行来玩一玩。
结论
我们对 TypeScript 的类型系统进行了详细讨论。下次,我们将学习函数并了解如何使用它们。