本文为《TypeScript入门与实战》的读书笔记,能深入全面的了解 TypeScript。掌握普通入门的用法就基本够用了,进阶部分后续用到再细看。应当依据项目实际需求来应用TypeScript,而不能仅局限于书本知识的学习。
本书是基于TypeScript 3.8版本编写的。
在阅读本书时,如果读者已经掌握了JavaScript语言的知识,那么可以跳过“TypeScript语言概览”部分,从“TypeScript类型系统”部分开始阅读。
示例代码均可在GitHub网站上找到,地址为 https://github.com/tstutorial/code 。
第1章 TypeScript简介
TypeScript是一门专为开发大规模JavaScript应用程序而设计的编程语言,是JavaScript的超集,包含了JavaScript现有的全部功能,并且使用了与JavaScript相同的语法和语义。
Type-Script编译器(tsc)将负责把TypeScript代码编译为JavaScript代码。TypeScript语言的基本设计原则之一:TypeScript应该生成简洁、符合编写习惯并易于识别的JavaScript代码;TypeScript不应该对代码进行激进的性能优化。
TypeScript中的静态类型是可选的,它不强制要求为程序中的每一部分都添加类型注解。TypeScript支持类型推断的功能,编译器能够自动推断出大部分表达式的类型信息,开发者只需要在程序中添加少量的类型注解便能拥有完整的类型信息。
TypeScript语言是开放的。微软公司实现的TypeScript编译器也是开源的,它的源代码托管在GitHub平台上并且使用了较为宽松的开源许可协议Apache License 2.0,该协议允许使用者对源代码进行修改发行以及用于商业用途。TypeScript语言是跨平台的。TypeScript程序经过编译后可以在任意的浏览器、JavaScript宿主环境和操作系统上运行。
开发者选择使用TypeScript语言至少有以下几点原因:
- 能够更早地发现代码中的错误。
- 能够帮助提高生产力。
- 支持JavaScript语言的最新特性并且使用了与JavaScript语言相同的语法和语义。
JavaScript是一门具有动态类型和弱类型的编程语言。其特点是数据类型检查发生在程序运行时,并且允许(隐式地)数据类型转换。JavaScript代码在真正运行前无法很好地检测代码中是否存在错误。
如果使用了TypeScript语言,那么在编译程序时就能够发现拼写错误。如果使用了支持TypeScript的代码编辑器,如Visual Studio Code,那么在编写代码的过程中就能够检查出拼写错误。
TypeScript提供了常用的代码重构工具。
下面列出了部分重构工具:
- 重命名符号名。
- 提取到函数或方法。
- 提取类型。
TypeScript还提供了一些代码快速修复工具
例如: - 自动删除未使用的声明。
- 自动删除执行不到的代码。
- 自动添加缺少的模块导入语句。
Visual Studio Code是微软公司开源的一款免费的跨平台的集成开发环境。它是使用TypeScript语言并基于Electron框架进行开发的。Angular是由Google公司推出的一款开源的Web应用程序框架。
当使用“Angular”这个名字时,我们指的是Angular 2.0及以上版本;而当使用“AngularJS”这个名字时,则特指Angular 1.x版本。Angular使用TypeScript语言对AngularJS进行了完全重写。
第2章 快速开始
本章主要内容:如何在线编写并运行TypeScript程序。如何在本地编写并运行TypeScript程序。
网页版的TypeScript编辑器,地址为 https://www.typescriptlang.org/play 。使用版本选择下拉列表能够快速地切换TypeScript版本,值得一提的是,在版本列表有一个特殊的版本“Nightly”,即“每日构建”版本,这是一种方法实践,采用了该方法的软件每天都会基于最新的程序源代码构建出一个版本,它能够让用户尽早地试用新版本的软件并提供反馈信息。
要使用 tsc
编译 TypeScript 项目,并指定 tsconfig.json
文件,你可以按照以下步骤操作:
1)初始化 tsconfig.json
在项目根目录下,运行以下命令来初始化一个 tsconfig.json
文件:
这将创建一个基本的 tsconfig.json
文件,包含一些默认的编译选项,例如将目标设置为 es5
,模块设置为 commonjs
,并启用严格类型检查。
2)指定需要编译的目录
若要指定需要编译的目录,可以使用 -b
或 --build
选项,后跟目录路径。例如:
这将只编译 src
文件夹中的 TypeScript 文件。如果 src
文件夹中没有 tsconfig.json
文件,tsc
会在该文件夹中创建一个。
3)使用多个 tsconfig.json 文件
你也可以同时指定多个 tsconfig.json
文件,tsc 会按照指定的顺序查找并编译这些文件。例如:
这将首先编译 src
文件夹中的 TypeScript 文件,然后使用 my.tsconfig.json
中的选项进行编译。
4)构建项目
在项目根目录下,运行以下命令来构建项目:
TypeScript 编译器会递归地查找并编译项目中所有 .ts
文件,使用 tsconfig.json
中指定的配置选项。
虽然Visual Studio Code支持编写TypeScript语言的程序,但是它并没有内置TypeScript编译器。
使用下面的命令全局安装TypeScript:npm install -g typescript
。使用下面的命令来验证TypeScript是否安装成功:tsc --version
。
创建文件:“tsconfig.json”是TypeScript编译器默认使用的配置文件。TypeScript源文件的常规扩展名为“.ts”。Visual Studio Code的任务管理器已经集成了对TypeScript编译器的支持,
使用快捷键“Ctrl + Shift + B”或从菜单栏里选择“Terminal→Run Build Task”来打开并运行构建任务面板,然后再选择“tsc: build - tsconfig.json”来编译TypeScript程序。在Visual Studio Code里,使用“Ctrl + ”(“
”为反引号,位于键盘上数字键1的左侧)快捷键打开命令行窗口。然后,使用Node.js命令行工具来运行。为了便于以后多次运行、编译TypeScript命令,我们可以将“tsc: build - tsconfig.json”设置为默认构建任务。使用快捷键“Ctrl + Shift + B”打开运行构建任务面板,点击右侧齿轮形状的配置按钮打开任务配置文件。
第3章 TypeScript语言基础
本章主要内容:变量声明和程序注释。JavaScript中的数据类型及字面量表示。典型的对象数据类型:对象、数组和函数。
TypeScript语言相当于JavaScript语言的“语法糖”。在计算机科学中,语法糖指的是编程语言里的某种语法,这种语法对语言的功能没有影响,但是会方便开发者的使用,能够让程序更加简洁,具有更高的可读性。
在JavaScript中,每个变量都有唯一的名字,也叫作标识符。标识符的定义规则如下:
- 允许包含字母、数字、下划线和美元符号“$”。
- 允许包含Unicode转义序列,如“\u0069\u{6F}”。
- 仅允许使用字母、Unicode转义序列、下划线和美元符号($)作为第一个字符,不允许使用数字作为第一个字符。
- 标识符区分大小写。
- 不允许使用保留字作为标识符。
使用let和const关键字能够声明具有块级作用域的变量,这弥补了var声明的不足。块语句用于将零条或多条语句组织在一起。在语法上,块语句使用一对大括号“{}”来表示。块级作用域指的就是块语句所创建的作用域,使用let声明和const声明的变量具有块级作用域,但是使用var声明的变量不具有块级作用域。
TypeScript支持三种类型的注释:
1) 单行注释与多行注释
单行注释使用双斜线“//”来表示,并且不允许换行。多行注释以“/”符号作为开始并以“/”符号作为结束。正如其名,多行注释允许换行。
2) 区域注释
区域注释不是一种新的注释语法,它借助单行注释的语法实现了定义代码折叠区域的功能。“//#region”定义了代码折叠区域的起始位置,“//#endregion”定义了代码折叠区域的结束位置。“区域描述”用于描述该折叠区域,当代码被折叠起来时,该描述信息会显示出来。
- 数据类型
ECMAScript 2015规范中定义了如下七种数据类型:其中,Undefined、Null、Boolean、String、Symbol和Number类型是原始数据类型,Object类型是非原始数据类型。原始数据类型是编程语言内置的基础数据类型,可用于构造复合类型。
String类型表示文本字符串,它由0个或多个字符构成。JavaScript使用UTF-16编码来表示一个字符。UTF-16编码以两个字节作为一个编码单元,每个字符使用一个编码单元或者两个编码单元来表示。
例如表情符号、数学符号或生僻字,你可能需要考虑码元和字符之间的差异。由于 length 统计的是码元而不是字符,如果你想得到字符的数量,你可以首先用它的迭代器分割字符串,它按字符进行迭代。
在获取字符串长度时,返回的是字符串中包含的编码单元的数量。ECMAScript 2015规定了字符串允许的最大长度为253 - 1,该数值也是JavaScript所能安全表示的最大整数。
Number:JavaScript使用双精度64位浮点数格式(IEEE 754)来表示数字,因此所有数字本质上都是浮点数。在该格式中,符号部分占用1位(bit),指数部分占用11位,小数部分占用52位,一共占用64位。
Symbol:Symbol值有一个重要特征,那就是每一个Symbol值都是唯一的且不可改变的。Symbol值的主要应用场景是作为对象的属性名。JavaScript提供了一个全局的“Symbol()”函数来创建Symbol类型的值。我们可以将“Symbol()”函数想象成GUID(全局唯一标识符)的生成器
JavaScript内置了一些所谓的Well-Known Symbol常量。这些Symbol常量用作对象属性名,它们的功能是定制对象的特定行为。
以下是11个常见的Well-Known Symbol常量:
- Symbol.iterator:用于定义一个对象是否可迭代。例如,数组、Map和Set对象都实现了这个符号。
- Symbol.hasInstance:用于判断一个对象是否是某个构造函数的实例。
- Symbol.isExtensible:用于检查对象是否可扩展。
- Symbol.ownKeys:返回一个包含对象自身可枚举属性的Symbol数组。
- Symbol.toStringTag:返回表示对象类型的字符串。
- Symbol.valueOf:返回对象的原始值。
- Symbol.toPrimitive:返回对象的原型值或原始值。
- Symbol.isInteger:用于判断一个值是否是整数。
- Symbol.isSafeInteger:用于判断一个值是否是安全整数(在JavaScript中,安全整数是-2^53到2^53-1之间的整数)。
- Symbol.asyncIterator:用于定义一个对象是否支持异步迭代。
- Symbol.asyncIteratorTag:用于标记一个对象是否具有Symbol.asyncIterator属性。
这些常量在编程中有重要的作用,尤其是在需要判断对象类型、检查属性是否存在或定制对象行为时。使用这些内置的Well-Known Symbol常量,可以使代码更具可读性和一致性,同时还能帮助预防程序中的错误。
Object:对象是属性的集合,对象属性使用键值来标识,键值只能为字符串或Symbol值。所有字符串(也包括空字符串)和Symbol值都是合法的键值。
Number字面量包含四类:二进制整数字面量、八进制整数字面量、十进制数字面量以及十六进制整数字面量。
字符串字面量是使用一对单引号或双引号包围起来的Unicode字符。字符串字面量中可以包含Unicode转义序列和十六进制转义序列。
模板字面量是ECMAScript 2015引入的新特性,它提供了一种语法糖来帮助构造字符串。
帮助开发者解决了一些长久以来的痛点,如动态字符串的拼接和创建多行字符串等。模板字符串的基本语法是使用反引号“`”,替换了字符串字面量中的单、双引号。
对象:在JavaScript中,对象属于非原始类型。
对象字面量的数据属性由属性名和属性值组成,对象属性名可以为标识符、字符串字面量和数字字面量,对象属性值可以为任意值。一个存取器属性由一个或两个存取器方法组成,存取器方法分为get方法和set方法两种。可计算属性名是指在定义对象字面量属性时使用表达式作为属性名。可计算属性名适用于对象属性名需要动态计算的场景之中。属性名表达式求值后将得到一个字符串或Symbol值,该字符串或Symbol值将被用作对象属性名。
原型对象:每个对象都有一个原型。对象的原型既可以是一个对象,即原型对象,也可以是null值。原型对象又有其自身的原型,因此对象的原型会形成一条原型链,原型链将终止于null值。原型对象本质上是一个普通对象,用于在不同对象之间共享属性和方法。
JavaScript中的继承机制也是通过原型来实现的。原型的作用主要体现在查询对象某个属性或方法时会沿着原型链依次向后搜索。如果对象本身和其原型对象上同时存在要访问的属性,那么就会产生遮蔽效果。在这种情况下,当前对象自身定义的属性拥有最高的优先级。在查询对象属性时会考虑对象的原型,但是在设置对象属性时不会考虑对象的原型,而是直接修改对象本身的属性值。
数组:数组表示一组有序元素的集合,它使用数字作为元素索引值。JavaScript中的数组不是独立的数据类型,它属于对象数据类型。
数组字面量是常用的创建数组的方法。数组字面量使用一对中括号“[]”将数组元素包含在内,数组元素之间使用逗号分隔。
数组元素可通过数字索引进行访问,索引值从零开始。当数组访问越界或使用了未知的索引时不会产生错误,而是会返回undefined值。
函数:JavaScript中的函数是“头等函数”,它具有以下特性:函数可以赋值给变量或对象属性。函数可以作为参数传递给另一个函数。函数可以作为函数返回值。
立即执行的函数表达式指的是在定义时就立即被调用的函数表达式。立即执行的函数表达式有两个特点:其一是函数表达式和函数声明一样,都能够创建出新的作用域,在函数内部声明的变量不会对函数外部产生影响,因此提供了一定的数据封装性;其二是立即执行的函数表达式自身对外部作用域没有任何影响。
箭头函数是ECMAScript 2015中新增的特性,用来定义匿名的函数表达式。箭头函数一定是匿名函数。箭头函数最显著的特点是使用“胖箭头”符号连接函数的形式参数列表和函数体。
箭头函数本身没有this绑定,它使用外层作用域中的this绑定。
第4章 TypeScript语言进阶
BigInt是在2019年9月被正式纳入ECMAScript标准中的特性。虽然BigInt不是频繁使用的特性,但其特殊性在于它是一种新的原始数据类型,同时又属于数值类型的一种。
JavaScript语言使用双精度64位浮点数格式来表示Number类型的值。Number类型能够安全表示的最大整数为253 - 1,该数值能够使用内置的Number对象上的MAX_SAFE_INTEGER属性来表示。BigInt类型能够表示任意精度的整数,尤其是大于253 - 1的整数,这也正是引入BigInt类型的原因。
两种方式来创建BigInt类型的值:使用BigInt字面量。使用BigInt()函数。
BigInt字面量的语法是在一个整数后面添加一个小写字母“n”。字母“n”必须紧随数字之后,两者之间不允许存在空白字符。BigInt()函数会尝试将传入的参数转换为BigInt,最基本的使用场景是将一个整数转换为BigInt类型的值。
在进行严格相等比较时,BigInt类型的值与Number类型的值永远不相等。在进行非严格相等比较及大小关系比较时,BigInt类型的值与Number类型的值将进行数学意义上的比较。
但是BigInt类型的值不允许与Number类型的值一起进行混合数学运算。
通过内置的Number()函数能够将BigInt类型的值转换为Number类型的值。但要注意,在BigInt类型与Number类型之间进行强制类型转换时有可能损失精度。
展开运算符是ECMAScript 2015中定义的运算符,可以用在多种上下文中,比如对象字面量、数组字面量和函数调用语句等。展开运算符使用连续的三个点符号“…”来表示。展开运算符的后面是一个表达式,表达式的求值结果为要展开的值。
JavaScript中的解构是指将数组或对象在结构上进行分解,将其拆分成独立的子结构,然后可以访问那些拆分后的子结构。
可选链运算符是2019年12月纳入ECMAScript标准中的新特性。当尝试访问对象属性时,如果对象的值为undefined或null,那么属性访问将产生错误。可选链运算符旨在帮助开发者省去冗长的undefined值和null值检查代码,增强了代码的表达能力。可选链运算符由一个问号和一个点号组成,即“?.”。可选链运算符有以下三种语法形式: 可选的静态属性访问。 可选的计算属性访问。 可选的函数调用或方法调用。
如果可选链运算符左侧操作数的求值结果为undefined或null,那么右侧的操作数不会再被求值,我们将这种行为称作短路求值。二元逻辑运算符“&&”和“||”也具有短路求值的特性。
空值合并运算符在ECMAScript 2020(ES11)引入的新特性空值合并运算符是一个新的二元逻辑运算符,它使用两个问号“??”作为标识。空值合并运算符与可选链运算符一样都具有短路求值的特性。当空值合并运算符左侧操作数的值不为undefined和null时,右侧操作数不会被求值,而是直接返回左侧操作数。
第5章 TypeScript类型基础
本章主要内容: 如何为程序添加静态类型信息。 TypeScript中的原始类型,如boolean类型、枚举类型和字面量类型等。 具有特殊性质的顶端类型和尾端类型。 与数组相关的数组类型和元组类型。 与对象相关的对象类型、函数类型、接口和类。 能够命名任意类型的类型别名。
- 1 类型注解
在TypeScript中,我们可以使用类型注解来明确标识类型。类型注解的语法由一个冒号“:”和某种具体类型“Type”组成TypeScript中的类型注解是可选的,编译器在大部分情况下都能够自动推断出表达式的类型。
- 2 类型检查
类型检查是验证程序中类型约束是否正确的过程。TypeScript提供了两种静态类型检查模式: 非严格类型检查(默认方式)。 TypeScript提供了若干个与严格类型检查相关的编译选项,例如“–strictNullChecks”和“–noImplicitAny”等。如果使用TypeScript官网提供的在线代码编辑器,那么这些严格类型检查编译选项是默认开启的。如果使用本地开发环境,那么可以在工程的tsconfig.json配置文件中启用“–strict”编译选项。
- 3 原始类型
JavaScript语言中的每种原始类型都有与之对应的TypeScript类型。除此之外,TypeScript还对原始类型进行了细化与扩展,增加了枚举类型和字面量类型等。
bigint:
TypeScript中的bigint类型对应于JavaScript中的BigInt原始类型。该类型能够表示任意精度的整数,但也仅能表示整数。bigint采用了特殊的对象数据结构来表示和存储一个整数。bigint类型使用bigint关键字来表示。
symbol与unique symbol:
TypeScript中的symbol类型对应于JavaScript中的Symbol原始类型。该类型能够表示任意的Symbol值。symbol类型使用symbol关键字来表示。symbol类型不同于其他原始类型,它不存在字面量形式。symbol类型的值只能通过“Symbol()”和“Symbol.for()”函数来创建或直接引用某个“Well-Known Symbol”值。为了能够将一个Symbol值视作表示固定值的字面量,TypeScript引入了“unique symbol”类型。“unique symbol”类型使用“unique symbol”关键字来表示。“unique symbol”类型的主要用途是用作接口、类等类型中的可计算属性名。TypeScript中只允许使用const声明或readonly属性声明来定义“unique symbol”类型的值。
“unique symbol”类型的值只允许使用“Symbol()”函数或“Symbol.for()”方法的返回值进行初始化,因为只有这样才能够“确保”引用了唯一的Symbol值。使用相同的参数调用“Symbol.for()”方法实际上返回的是相同的Symbol值。因此,可能出现多个“unique symbol”类型的值实际上是同一个Symbol值的情况。由于设计上的局限性,TypeScript目前无法识别出这种情况,因此不会产生编译错误,开发者必须要留意这种特殊情况。在设计上,每一个“unique symbol”类型都是一种独立的类型。在不同的“unique symbol”类型之间不允许相互赋值;在比较两个“unique symbol”类型的值时,也将永远返回false。由于“unique symbol”类型是 symbol类型的子类型,因此可以将“unique symbol”类型的值赋值给symbol类型。
Nullable:
TypeScript中的Nullable类型指的是值可以为undefined或null的类型。编译选项“–strictNullChecks”,即严格的null检查模式。实际上它同时作用于undefined类型和null类型的类型检查。在默认情况下,“–strictNullChecks”编译选项没有被启用。除尾端类型外所有类型都能够接受undefined值和null值。例如,在没有启用“–strictNullChecks”编译选项时,允许将undefined值和null值赋值给string类型等其他类型。
该模式存在一个明显的问题,就是无法检查出空引用的错误。例如,已知某一个变量的类型是string,于是通过访问其length属性来获取该变量表示的字符串的长度。但如果string类型的变量值可以为undefined或null,那么这段代码在运行时将产生错误。当启用了“–strictNullChecks”编译选项时,undefined值和null值不再能够赋值给不相关的类型。启用了“–strictNullChecks”编译选项时,undefined值只能够赋值给undefined类型,null值只能够赋值给null类型,实际上这种表述不完全准确。因为在该模式下,undefined值和null值允许赋值给顶端类型,同时undefined值也允许赋值给void类型。
void:
void类型表示某个值不存在,该类型用作函数的返回值类型。
当启用了“–strictNullChecks”编译选项时,只允许将undefined值赋值给void类型。
如果没有启用“–strictNullChecks”编译选项,那么允许将undefined值和null值赋值给void类型。
- 4 枚举类型
枚举类型由零个或多个枚举成员构成,每个枚举成员都是一个命名的常量。在TypeScript中,枚举类型是一种原始类型,它通过enum关键字来定义。
1)数值型枚举:
是number类型的子类型,它由一组命名的数值常量构成。
在使用枚举成员时,可以像访问对象属性一样访问枚举成员。
每个数值型枚举成员都表示一个具体的数字。如果在定义枚举时没有设置枚举成员的值,那么TypeScript将自动计算枚举成员的值。根据TypeScript语言的规则,第一个枚举成员的值为0,其后每个枚举成员的值等于前一个枚举成员的值加1。
数值型枚举是number类型的子类型,因此允许将数值型枚举类型赋值给number类型。
number类型也能够赋值给枚举类型,即使number类型的值不在枚举成员值的列表中也不会产生错误。
2)字符串枚举:
字符串枚举成员必须使用字符串字面量或另一个字符串枚举成员来初始化。字符串枚举成员没有自增长的行为。
字符串枚举是string类型的子类型,因此允许将字符串枚举类型赋值给string类型。
但是反过来,不允许将string类型赋值给字符串枚举类型,这一点与数值型枚举是不同的。
3)异构型枚举:
TypeScript允许在一个枚举中同时定义数值型枚举成员和字符串枚举成员,我们将这种类型的枚举称作异构型枚举。异构型枚举在实际代码中很少被使用,虽然在语法上允许定义异构型枚举,但是不推荐在代码中使用异构型枚举。我们可以尝试使用对象来代替异构型枚举。
4)枚举成员映射:
不论是哪种类型的枚举,都可以通过枚举成员名去访问枚举成员值。
对于数值型枚举,不但可以通过枚举成员名来获取枚举成员值,也可以反过来通过枚举成员值去获取枚举成员名。
5)常量枚举成员与计算枚举成员:
每个枚举成员都有一个值,根据枚举成员值的定义可以将枚举成员划分为以下两类:常量枚举成员;计算枚举成员。若枚举类型的第一个枚举成员没有定义初始值,那么该枚举成员是常量枚举成员并且初始值为0。
若枚举成员没有定义初始值并且与之紧邻的前一个枚举成员值是数值型常量,那么该枚举成员是常量枚举成员并且初始值为紧邻的前一个枚举成员值加1。如果紧邻的前一个枚举成员的值不是数值型常量,那么将产生错误。若枚举成员的初始值是常量枚举表达式,那么该枚举成员是常量枚举成员。常量枚举表达式是TypeScript表达式的子集,它能够在编译阶段被求值。
常量枚举表达式的具体规则如下:
- 常量枚举表达式可以是数字字面量、字符串字面量和不包含替换值的模板字面量。
- 常量枚举表达式可以是对前面定义的常量枚举成员的引用。
- 常量枚举表达式可以是用分组运算符包围起来的常量枚举表达式。
- 常量枚举表达式中可以使用一元运算符“+”“-”“~”,操作数必须为常量枚举表达式。
- 常量枚举表达式中可以使用二元运算符“+”“-”“”“*”“/”“%”“<<”“>>”“>>>”“&”“|”“^”,两个操作数必须为常量枚举表达式。字面量枚举成员是指不带有初始值的常量枚举成员,或者是指其值被初始化为任何字符串字面量(例如:”foo”、”bar”、”baz”)或任何数字字面量(例如:1、100)以及应用了一元负符号的数字字面量(例如:-1、-100)。
当所有枚举成员都拥有字面量枚举值时,字面量枚举值就具有一种特殊的语义,即它们可以作为类型使用。在接口约束中,字面量枚举成员能够限制属性的类型和值,从而提供类型安全。字面量枚举成员是常量枚举成员的子集。除常量枚举成员之外的其他枚举成员都属于计算枚举成员。
6)联合枚举类型:
当枚举类型中的所有成员都是字面量枚举成员时,该枚举类型成了联合枚举类型。
联合枚举类型中的枚举成员除了能够表示一个常量值外,还能够表示一种类型,即联合枚举成员类型。联合枚举成员类型是联合枚举类型的子类型,因此可以将联合枚举成员类型赋值给联合枚举类型。
7)const枚举类型:
枚举类型是TypeScript对JavaScript的扩展,JavaScript语言本身并不支持枚举类型。在编译时,TypeScript编译器会将枚举类型编译为JavaScript对象。
const枚举类型将在编译阶段被完全删除,并且在使用了const枚举类型的地方会直接将const枚举成员的值内联到代码中。
const枚举类型使用“const enum”关键字定义。为了便于代码调试和保持代码的可读性,TypeScript编译器在内联了const枚举成员的位置还额外添加了注释,注释的内容为枚举成员的名字。
- 5 字面量类型
TypeScript支持将字面量作为类型使用,我们称之为字面量类型。每一个字面量类型都只有一个可能的值,即字面量本身。
1)boolean字面量类型
boolean字面量类型只有以下两种: true字面量类型。 false字面量类型。
2)string字面量类型字符串字面量和模板字面量都能够创建字符串。
字符串字面量和不带参数的模板字面量可以作为string字面量类型使用。
3)数字字面量类型数字字面量类型包含以下两类: number字面量类型。 bigint字面量类型。所有的二进制、八进制、十进制和十六进制数字字面量都可以作为数字字面量类型。
除了正数数值外,负数也可以作为数字字面量类型。
4)枚举成员字面量类型
联合枚举成员类型使用枚举成员字面量形式表示。
- 6 单元类型
单元类型(Unit Type)也叫作单例类型(Singleton Type),指的是仅包含一个可能值的类型。
TypeScript中的单元类型有以下几种: undefined类型。 null类型。 unique symbol类型。 void类型。 字面量类型。 联合枚举成员类型。
- 7 顶端类型
顶端类型是一种通用类型,有时也称为通用超类型,顶端类型涵盖了类型系统中所有可能的值。TypeScript中有以下两种顶端类型: any; unknown。
1) any
在TypeScript中,所有类型都是any类型的子类型。我们可以将任何类型的值赋值给any类型。
需要注意的是,虽然any类型是所有类型的父类型,但是TypeScript允许将any类型赋值给任何其他类型。
在any类型上允许执行任意的操作而不会产生编译错误。
在将已有的JavaScript程序迁移到TypeScript程序的过程中,使用any类型来暂时绕过类型检查是一项值得掌握的技巧。
若一个值没有明确的类型注解,编译器又无法自动推断出它的类型,那么这个值的默认类型为any类型。
TypeScript提供了一个“–noImplicitAny”编译选项来控制该行为。当启用了该编译选项时,如果发生了隐式的any类型转换,那么会产生编译错误。
在“tsconfig.json”配置文件中启用“–noImplicitAny”编译选项
2) unknown
根据顶端类型的性质,任何其他类型都能够赋值给unknown类型,该行为与any类型是一致的。
unknown类型是比any类型更安全的顶端类型,因为unknown类型只允许赋值给any类型和unknown类型,而不允许赋值给任何其他类型,该行为与any类型是不同的。
同时,在unknown类型上也不允许执行绝大部分操作。
在使用unknown类型的参数message时,编译器会强制我们将其细化为某种具体类型。
- 8 尾端类型
在类型系统中,尾端类型(Bottom Type)是所有其他类型的子类型。
尾端类型中不包含任何值。尾端类型也称作0类型或者空类型。TypeScript中只存在一种尾端类型,即never类型。never类型允许赋值给任何类型,尽管并不存在never类型的值。
没有类型是never类型的子类型。因此,除never类型自身外,所有其他类型都不能够赋值给never类型。
never类型主要有以下几种典型的应用场景。场景一 never类型可以作为函数的返回值类型,它表示该函数无法返回一个值。场景二 在“条件类型”中常使用never类型来帮助完成一些类型运算。最后一个要介绍的never类型的应用场景与类型推断功能相关。
在TypeScript编译器执行类型推断操作时,如果发现已经没有可用的类型,那么推断结果为never类型。
- 9 数组类型
在TypeScript中,数组值的数据类型为数组类型。
数组类型定义:
1) 简便数组类型表示法const digits: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
简便数组类型表示法借用了数组字面量的语法,通过在数组元素类型之后添加一对方括号“[]”来定义数组类型。
如果数组中元素的类型为复合类型,则需要在数组元素类型上使用分组运算符,即小括号。
在使用简便数组类型表示法时,必须先将联合类型放在分组运算符内,然后再在后面添加一对方括号。
2) 泛型数组类型表示法
示例:const red: Array<string | number> = ['f', 'f', 0, 0, 0, 0];
,let b: Array<{ x: number; y: number }>;
泛型数组类型表示法就是使用泛型来表示数组类型。
在定义简单数组类型时,如数组元素为单一原始类型或类型引用,使用简便数组类型表示法更加清晰和简洁。
如果数组元素是复杂类型,如对象类型和联合类型等,则可以选择使用泛型数组类型表示法。它也许能让代码看起来更加整洁一些。
数组元素类型:
当访问数组中不存在的元素时将返回undefined值。TypeScript的类型系统无法推断出是否存在数组访问越界的情况,因此即使访问了不存在的数组元素,还是会得到声明的数组元素类型。
只读数组:
TypeScript提供了以下三种方式来定义一个只读数组:- 使用“ReadonlyArray<T>”
内置类型。- 使用readonly修饰符。- 使用“Readonly<T>”
工具类型。以上三种定义只读数组的方式只是语法不同,它们在功能上没有任何差别。
在定义只读数组时,将readonly修饰符置于数组类型之前即可。
注意,readonly修饰符不允许与泛型数组类型表示法一起使用。
从TypeScript 3.4开始可以使用“Readonly<T>”
工具类型来定义只读数组。
示例如下:const red: Readonly<number[]> = [255, 0, 0];
需要注意的是,类型参数T的值为数组类型“number[]”,而不是数组元素类型number。在这一点上,它与“ReadonlyArray<T>”
类型是有区别的。
可以通过数组元素索引来访问只读数组元素,但是不能修改只读数组元素。
在只读数组上也不支持任何能够修改数组元素的方法,如push和pop方法等。
在进行赋值操作时,允许将常规数组类型赋值给只读数组类型,但是不允许将只读数组类型赋值给常规数组类型。换句话说,不能通过赋值操作来放宽对只读数组的约束。
- 10 元组类型
元组(Tuple)表示由有限元素构成的有序列表。在TypeScript中,元组类型是数组类型的子类型。元组是长度固定的数组,并且元组中每个元素都有确定的类型。
示例: const score: [string, number] = ['math', 100];
元组中每个元素的类型不必相同。
元组的值实际上是一个数组,在给元组类型赋值时,数组中每个元素的类型都要与元组类型的定义保持兼容。在给元组类型赋值时,还要保证数组中元素的数量与元组类型定义中元素的数量保持一致,否则将产生编译错误。
只读元组:定义只读元组有以下两种方式:使用readonly修饰符。 使用“Readonly<T>”
工具类型。不能通过赋值操作来放宽对只读元组的约束。可以使用访问数组元素的方法去访问元组中的元素。
当访问数组中不存在的元素时不会产生编译错误。与之不同的是,当访问元组中不存在的元素时会产生编译错误。修改元组元素值的方法与修改数组元素值的方法相同。在定义元组时,可以将某些元素定义为可选元素。定义元组可选元素的语法是在元素类型之后添加一个问号“?”。如果元组中同时存在可选元素和必选元素,那么可选元素必须位于必选元素之后,在给元组赋值时,可以不给元组的可选元素赋值。
在定义元组类型时,可以将最后一个元素定义为剩余元素。const tuple: [number, ...string[]] = [0, 'a', 'b'];
,在元组tuple的定义中包含了剩余元素。其中,元组的第一个元素为number类型,其余的元素均为string类型。
如果元组类型的定义中含有剩余元素,那么该元组的元素数量是开放的,它可以包含零个或多个指定类型的剩余元素。
元组的长度:
对于经典的元组类型,即不包含可选元素和剩余元素的元组而言,元组中元素的数量是固定的。也就是说,元组拥有一个固定的长度。TypeScript编译器能够识别出元组的长度并充分利用该信息来进行类型检查。
当元组中包含了可选元素时,元组的长度不再是一个固定值。编译器能够根据元组可选元素的数量识别出元组所有可能的长度,进而构造出一个由数字字面量类型构成的联合类型来表示元组的长度。
若元组类型中定义了剩余元素,那么该元组拥有不定数量的元素。因此,该元组length属性的类型将放宽为number类型。
元组类型与数组类型的兼容性:
在进行赋值操作时,允许将元组类型赋值给类型兼容的元组类型和数组类型。
元组类型允许赋值给常规数组类型和只读数组类型,但只读元组类型只允许赋值给只读数组类型。
不允许将数组类型赋值给元组类型。
- 11 对象类型
1)Object
这里的Object指的是Object类型,而不是JavaScript内置的“Object()”构造函数。
Object类型是特殊对象“Object.prototype”的类型,该类型的主要作用是描述JavaScript中几乎所有对象都共享(通过原型继承)的属性和方法。
Object类型有一个特点,那就是除了undefined值和null值外,其他任何值都可以赋值给Object类型。
JavaScript语言中存在自动封箱操作。当在原始值上调用某个方法时,JavaScript会对原始值执行封箱操作,将其转换为对象类型,然后再调用相应方法。Object类型描述了所有对象共享的属性和方法,而JavaScript允许在原始值上直接访问这些方法,因此TypeScript允许将原始值赋值给Object类型。
2)object
object类型使用object关键字作为标识,object类型名中的字母全部为小写。object类型的关注点在于类型的分类,它强调一个类型是非原始类型,即对象类型。不允许读取和修改object类型上的自定义属性。
在object类型上仅允许访问对象的公共属性和方法,也就是Object类型中定义的属性和方法。
只有非原始类型,也就是对象类型能够赋给object类型。
object类型仅能够赋值给以下三种类型: 顶端类型any和unknown。 Object类型。 空对象类型字面量“{}”。
3)对象类型字面量
在定义对象类型字面量时,需要将类型成员依次列出。
类型成员置于一对大括号“{}”之内。在各个类型成员之间,不但可以使用分号“;”进行分隔,还可以使用逗号“,”进行分隔,这两种分隔符不存在功能上的差异。
对象类型字面量的类型成员可分为以下五类: 属性签名; 调用签名; 构造签名; 方法签名; 索引签名
以属性签名为例来介绍对象类型字面量的使用方法,其他种类的类型成员将在5.12节和5.13节中进行详细介绍。
属性签名声明了对象类型中属性成员的名称和类型。
在该语法中,PropertyName表示对象属性名,可以为标识符、字符串、数字和可计算属性名;Type表示该属性的类型。
在属性签名的语法中,表示类型的Type部分是可以省略的,允许只列出属性名而不定义任何类型。在这种情况下,该属性的类型默认为any类型。
若启用了“–noImplicitAny”编译选项,则会产生编译错误,因为对象属性隐式地获得了any类型。
在默认情况下,通过属性签名定义的对象属性是必选属性。如果在属性签名中的属性名之后添加一个问号“?”,那么将定义一个可选属性。
在给对象类型赋值时,可选属性可以被忽略。
在“–strictNullChecks”模式下,TypeScript会自动在可选属性的类型定义中添加undefined类型。
该行为的结果是,我们可以为可选属性传入undefined值来明确地表示忽略该属性的值
在操作对象类型的值时,只允许读写对象类型中已经定义的必选属性和可选属性。
在属性签名定义中添加readonly修饰符能够定义对象只读属性。
如果对象类型字面量没有定义任何类型成员,那么它就成了一种特殊的类型,即空对象类型字面量“{}”。空对象类型字面量表示不带有任何属性的对象类型,因此不允许在“{}”类型上访问任何自定义属性。
在空对象类型字面量“{}”上,允许访问对象公共的属性和方法,也就是Object类型上定义的方法和属性。
空对象类型字面量“{}”与Object类型十分相似。
单从行为上来看两者是可以互换使用的。例如,除了undefined值和null值外,其他任何值都可以赋值给空对象类型字面量“{}”和Object类型。同时,空对象类型字面量“{}”和Object类型之间也允许互相赋值。
两者的区别主要在于语义上。全局的Object类型用于描述对象公共的属性和方法,它相当于一种专用类型,因此程序中不应该将自定义变量、参数等类型直接声明为Object类型。空对象类型字面量“{}”强调的是不包含属性的对象类型,同时也可以作为Object类型的代理来使用。最后,也要注意在某些场景中新的object类型可能是更加合适的选择。
4)弱类型
弱类型(Weak Type)是TypeScript 2.4版本中引入的一个概念。弱类型指的是同时满足以下条件的对象类型: 对象类型中至少包含一个属性。 对象类型中所有属性都是可选属性。 对象类型中不包含字符串索引签名、数值索引签名、调用签名和构造签名。
5)多余属性
对象多余属性可简单理解为多出来的属性。多余属性会对类型间关系的判定产生影响。
多余属性是一个相对的概念,只有在比较两个对象类型的关系时谈论多余属性才有意义。
多余属性检查是TypeScript 1.6引入的功能。多余属性会影响类型间的子类型兼容性以及赋值兼容性,也就是说编译器不允许在一些操作中存在多余属性。例如,将对象字面量赋值给变量或属性时,或者将对象字面量作为函数参数来调用函数时,编译器会严格检查是否存在多余属性。若存在多余属性,则会产生编译错误。
多余属性检查能够带来的最直接的帮助是发现属性名的拼写错误。
能够忽略多余属性检查的方法如下:
使用类型断言,这是推荐的方法。类型断言能够对类型进行强制转换。类型断言能够绕过多余属性检查的真正原因是,处于类型断言表达式中的对象字面量将不再是“全新的对象字面量类型”,因此编译器也就不会对其进行多余属性检查
启用“–suppressExcessPropertyErrors”编译选项。启用该编译选项能够完全禁用整个TypeScript工程的多余属性检查,但同时也将完全失去多余属性检查带来的帮助。
使用“// @ts-ignore”注释指令。该注释指令能够禁用针对某一行代码的类型检查。
为目标对象类型添加索引签名。若目标对象类型上存在索引签名,那么目标对象可以接受任意属性,因此也就谈不上多余属性。
最后一种方法也许不是很好理解。如果我们先将对象字面量赋值给某个变量,然后再将该变量赋值给目标对象类型,那么将不会执行多余属性检查。这种方法能够生效的原理与类型断言类似,那就是令源对象类型不为“全新的对象字面量类型”,于是编译器将不执行多余属性检查。
代码的第4行,赋值语句右侧不是对象字面量,而是一个标识符,因此temp的类型不是“全新的对象字面量类型。
- 12 函数类型
12.1 常规参数类型
在函数形式参数列表中,为参数添加类型注解就能够定义参数的类型。
如果在函数形式参数列表中没有明确指定参数类型,并且编译器也无法推断参数类型,那么参数类型将默认为any类型。
12.2 可选参数类型
在函数形式参数名后面添加一个问号“?”就可以将该参数声明为可选参数。
函数的可选参数必须位于函数参数列表的末尾位置。
如果函数的某个参数是可选参数,那么在调用该函数时既可以传入对应的实际参数,也可以完全不传入任何实际参数。
12.3 默认参数类型
函数默认参数类型可以通过类型注解定义,也可以根据默认参数值自动地推断类型。
如果函数定义了默认参数,并且默认参数处于函数参数列表末尾的位置,那么该参数将被视为可选参数,在调用该函数时可以不传入对应的实际参数值。
在语法上,同一个函数参数不允许同时声明为可选参数和默认参数,否则将产生编译错误。
如果默认参数之后存在必选参数,那么该默认参数不是可选的参数,在调用函数时必须传入对应的实际参数值。
12.4 剩余参数类型
必选参数、可选参数和默认参数处理的都是单个参数,而剩余参数处理的则是多个参数。如果函数定义中声明了剩余参数,那么在调用函数时会将多余的实际参数收集到剩余参数列表中。
剩余参数的类型应该为数组类型或元组类型。
12.5 解构参数类型
可以使用类型注解为解构参数添加类型信息。
12.6 返回值类型
可以使用类型注解为函数添加返回值类型。
在绝大多数情况下,TypeScript能够根据函数体内的return语句等自动推断出返回值类型,因此我们也可以省略返回值类型。
在TypeScript的原始类型里有一个特殊的空类型void,该类型唯一有意义的使用场景就是作为函数的返回值类型。如果一个函数的返回值类型为void,那么该函数只能返回undefined值。这意味着函数明确地返回了一个undefined值,或者函数没有调用return语句,在这种情况下函数默认返回undefined值。
如果没有启用“–strictNullChecks”编译选项,那么void返回值类型也允许返回null值。
12.7 函数类型字面量
函数类型字面量的语法与箭头函数的语法相似,
ParameterList表示可选的函数形式参数列表;Type表示函数返回值类型;形式参数列表与返回值类型之间使用胖箭头“=>”连接。
在函数类型字面量中定义函数参数的类型时,必须包含形式参数名,不允许只声明参数的类型。
函数类型字面量中的形式参数名与实际函数值中的形式参数名不必相同。
函数类型字面量中的返回值类型必须明确指定,不允许省略。如果函数没有返回值,则需要指定void类型作为返回值类型。
12.8 调用签名
函数在本质上是一个对象,但特殊的地方在于函数是可调用的对象。
若在对象类型中定义了调用签名类型成员,那么我们称该对象类型为函数类型。
在该语法中,ParameterList表示函数形式参数列表类型,Type表示函数返回值类型,两者都是可选的。
我们使用对象类型字面量和调用签名定义了一个函数类型,该函数类型接受两个number类型的参数,并返回number类型的值:
函数类型字面量是仅包含单个调用签名的对象类型字面量的简写形式
函数类型字面量的优点是简洁,而对象类型字面量的优点是具有更强的类型表达能力。
12.9 构造函数类型字面量
在面向对象编程中,构造函数是一类特殊的函数,它用来创建和初始化对象。JavaScript中的函数可以作为构造函数使用,在调用构造函数时需要使用new运算符。
构造函数类型字面量是定义构造函数类型的方法之一,它能够指定构造函数的参数类型、返回值类型以及
泛型类型参数。
JavaScript提供了一个内置的Error构造函数,它接受一个可选的message作为参数并返回新创建的Error对象。
12.10 构造签名
若在对象类型中定义了构造签名类型成员,那么我们称该对象类型为构造函数类型。{ new (ParameterList): Type}
,在该语法中,new是运算符关键字,ParameterList表示构造函数形式参数列表类型,Type表示构造函数返回值类型,两者都是可选的。
构造函数类型字面量是仅包含单个构造签名的对象类型字面量的简写形式。
12.11 调用签名与构造签名
若在对象类型中同时定义调用签名和构造签名,则能够表示既可以被直接调用,又可以作为构造函数使用的函数类型。
12.12 重载函数
重载函数是指一个函数同时拥有多个同类的函数签名。
在使用函数声明定义函数时能够定义重载函数。重载函数的定义由以下两部分组成: 一条或多条函数重载语句。 一条函数实现语句。
不带有函数体的函数声明语句叫作函数重载。
函数重载的语法中不包含函数体,它只提供了函数的类型信息。函数重载只存在于代码编译阶段,在编译生成JavaScript代码时会被完全删除,因此在最终生成的JavaScript代码中不包含函数重载的代码。
在函数重载中,不允许使用默认参数。函数重载应该位于函数实现(将在下一节中介绍)之前,每一个函数重载中的函数名和函数实现中的函数名必须一致。
同时需要注意,在各个函数重载语句之间以及函数重载语句与函数实现语句之间不允许出现任何其他语句,否则将产生编译错误。
函数实现包含了实际的函数体代码,该代码不仅在编译时存在,在编译生成的JavaScript代码中同样存在。每一个重载函数只允许有一个函数实现,并且它必须位于所有函数重载语句之后,否则将产生编译错误。
TypeScript中的重载函数最令人迷惑的地方在于,函数实现中的函数签名不属于重载函数的调用签名之一,只有函数重载中的函数签名能够作为重载函数的调用签名。
函数实现需要兼容每个函数重载中的函数签名,函数实现的函数签名类型必须能够赋值给函数重载的函数签名类型。
在TypeScript中,重载函数只存在一个函数实现,开发者需要在这个唯一的函数实现中实现所有函数重载的功能。这就需要开发者自行去检测参数的类型及数量,并根据判断结果去执行不同的操作。
TypeScript不支持为不同的函数重载分别定义不同的函数实现。从这点上来看,TypeScript中的函数重载不是特别便利。
函数重载解析顺序:
一个函数重载需要满足如下条件才能成为本次函数调用的候选函数重载:函数实际参数的数量不少于函数重载中定义的必选参数的数量。函数实际参数的数量不多于函数重载中定义的参数的数量。每个实际参数的类型能够赋值给函数重载定义中对应形式参数的类型。候选函数重载列表中的成员将以函数重载的声明顺序作为初始顺序,然后进行简单的排序,将参数类型中包含字面量类型的函数重载排名提前。
若候选函数重载列表中存在一个或多个函数重载,则使用列表中第一个函数重载。
如果构建的候选函数重载列表为空列表,则会产生编译错误。
开发者在编写函数重载代码时一定要将最精确的函数重载定义放在最前面,因为它们定义的顺序将影响函数调用签名的选择。
实际上在很多场景中我们并不需要声明重载函数,尤其是在函数返回值类型不变的情况下。
重载函数的类型可以通过包含多个调用签名的对象类型来表示。
在定义重载函数的类型时,有以下两点需要注意: 函数实现的函数签名不属于重载函数的调用签名之一。 调用签名的书写顺序是有意义的,它决定了函数重载的解析顺序,一定要确保更精确的调用签名位于更靠前的位置。
12.13 函数中this值的类型
this是JavaScript中的关键字,它可以表示调用函数的对象或者实例对象等。在默认情况下,编译器会将函数中的this值设置为any类型,并允许程序在this值上执行任意的操作。因为,编译器不会对any类型进行类型检查。TypeScript提供了一个“–noImplicitThis”编译选项。当启用了该编译选项时,如果this值默认获得了any类型,那么将产生编译错误;TypeScript支持在函数形式参数列表中定义一个特殊的this参数来描述该函数中this值的类型。
this参数固定使用this作为参数名。this参数是一个可选的参数,若存在,则必须作为函数形式参数列表中的第一个参数。this参数的类型即为函数体中this值的类型。this参数不同于常规的函数形式参数,它只存在于编译阶段,在编译生成的JavaScript代码中会被完全删除,在运行时的代码中不存在这个this参数。
如果我们想要定义一个纯函数或者是不想让函数代码依赖于this的值,那么在这种情况下可以明确地将this参数定义为void类型。 “Function.prototype.call()”方法是JavaScript内置的方法,它能够指定调用函数时使用的this值。
- 13 接口
类似于对象类型字面量,接口类型也能够表示任意的对象类型。不同的是,接口类型能够给对象类型命名以及定义类型参数。接口类型无法表示原始类型,如boolean类型等。
接口声明只存在于编译阶段,在编译后生成的JavaScript代码中不包含任何接口代码。
13.1 接口声明
通过接口声明能够定义一个接口类型。
在该语法中,interface是关键字,InterfaceName表示接口名,它必须是合法的标识符,TypeMember表示接口的类型成员,所有类型成员都置于一对大括号“{}”之内。
按照惯例,接口名的首字母需要大写。
从语法的角度来看,接口声明就是在对象类型字面量之前添加了interface关键字和接口名。因此,在11.3节中介绍的语法规则同样适用于接口声明。
同样地,接口类型的类型成员也分为以下五类:- 属性签名- 调用签名- 构造签名- 方法签名- 索引签名
13.2 属性签名
示例:
属性签名声明了对象类型中属性成员的名称和类型。
13.3 调用签名
示例:
调用签名定义了该对象类型表示的函数在调用时的类型参数、参数列表以及返回值类型。
13.4 构造签名
示例:
构造签名定义了该对象类型表示的构造函数在使用new运算符调用时的参数列表以及返回值类型。
13.5 方法签名
方法签名是声明函数类型的属性成员的简写。
示例:
从语法的角度来看,方法签名是在调用签名之前添加一个属性名作为方法名。
若接口中包含多个名字相同但参数列表不同的方法签名成员,则表示该方法是重载方法。
13.6 索引签名
JavaScript支持使用索引去访问对象的属性,即通过方括号“[]”语法去访问对象属性。
接口中的索引签名能够描述使用索引访问的对象属性的类型。索引签名只有以下两种:字符串索引签名。 数值索引签名。
13.6.1 字符串索引签名[IndexName: string]: Type
,在该语法中,IndexName表示索引名,它可以为任意合法的标识符。索引名只起到占位的作用,它不代表真实的对象属性名;在字符串索引签名中,索引名的类型必须为string类型;Type表示索引值的类型,它可以为任意类型。
一个接口中最多只能定义一个字符串索引签名。字符串索引签名会约束该对象类型中所有属性的类型。
13.6.2 数值索引签名[IndexName: number]: Type
,在该语法中,IndexName表示索引名,它可以为任意合法的标识符。索引名只起到占位的作用,它不代表真实的对象属性名;在数值索引签名中,索引名的类型必须为number类型;Type表示索引值的类型,它可以为任意类型。
一个接口中最多只能定义一个数值索引签名。数值索引签名约束了数值属性名对应的属性值的类型。
若接口中同时存在字符串索引签名和数值索引签名,那么数值索引签名的类型必须能够赋值给字符串索引签名的类型。
虽然JavaScript也允许使用数字等其他值作为对象的索引,但最终它们都会被转换为字符串类型。因此,数值索引签名能够表示的属性集合是字符串索引签名能够表示的属性集合的子集。
13.7 可选属性与方法
在默认情况下,接口中属性签名和方法签名定义的对象属性都是必选的。在给接口类型赋值时,如果未指定必选属性则会产生编译错误。
可以在属性名或方法名后添加一个问号“?”,从而将该属性或方法定义为可选的。
如果接口中定义了重载方法,那么所有重载方法签名必须同时为必选的或者可选的。
13.8 只读属性与方法
在接口声明中,使用readonly修饰符能够定义只读属性。readonly修饰符只允许在属性签名和索引签名中使用
如果接口中既定义了只读索引签名,又定义了非只读的属性签名,那么非只读的属性签名定义的属性依旧是非只读的,除此之外的所有属性都是只读的。
13.9 接口的继承
接口可以继承的对象类型如下:接口。对象类型的类型别名。类。对象类型的交叉类型。
接口的继承需要使用extends关键字。
一个接口可以同时继承多个接口,父接口名之间使用逗号分隔。
当一个接口继承了其他接口后,子接口既包含了自身定义的类型成员,也包含了父接口中的类型成员。
如果子接口与父接口之间存在同名的类型成员,那么子接口中的类型成员具有更高的优先级。同时,子接口与父接口中的同名类型成员必须是类型兼容的。也就是说,子接口中同名类型成员的类型需要能够赋值给父接口中同名类型成员的类型,否则将产生编译错误。
如果仅是多个父接口之间存在同名的类型成员,而子接口本身没有该同名类型成员,那么父接口中同名类型成员的类型必须是完全相同的,否则将产生编译错误。
- 14 类型别名
14.1 类型别名声明
类型别名声明能够定义一个类型别名,type AliasName = Type
,在该语法中,type是声明类型别名的关键字;AliasName表示类型别名的名称;Type表示类型别名关联的具体类型。
类型别名的首字母通常需要大写。同时需要注意,不能使用TypeScript内置的类型名作为类型别名的名称,例如boolean、number和any等。
类型别名引用的类型可以为任意类型,例如原始类型、对象类型、联合类型和交叉类型等。在类型别名中,也可以引用其他类型别名。使用类型别名不但能够简化代码,还能够给该类型起一个具有描述性的名字。
14.2 递归的类型别名
在TypeScript 3.7版本中,编译器对类型别名的解析进行了一些优化。在类型别名所引用的类型中,使用惰性求值的策略来解析泛型类型参数。因此,允许在泛型类型参数中递归地使用类型别名。总结起来,目前允许在以下场景中使用递归的类型别名:1)若类型别名引用的类型为接口类型、对象类型字面量、函数类型字面量和构造函数类型字面量,则允许递归引用类型别名。2)若类型别名引用的是数组类型或元组类型,则允许在元素类型中递归地引用类型别名。3)若类型别名引用的是泛型类或泛型接口,则允许在类型参数中递归的引用类型别名。
14.3 类型别名与接口
区别之一,类型别名能够表示非对象类型,而接口则只能表示对象类型。因此,当我们想要表示原始类型、联合类型和交叉类型等类型时只能使用类型别名。
区别之二,接口可以继承其他的接口、类等对象类型,而类型别名则不支持继承。
区别之三,接口名总是会显示在编译器的诊断信息(例如,错误提示和警告)和代码编辑器的智能提示信息中,而类型别名的名字只在特定情况下才会显示出来。
只有当类型别名表示数组类型、元组类型以及类或接口的泛型实例类型时,才会在相关提示信息中显示类型别名的名字。
区别之四,接口具有声明合并的行为,而类型别名则不会进行声明合并。
- 15 类
15.1 类的定义
虽然JavaScript语言支持了类,但其本质上仍是函数,类是一种语法糖。TypeScript语言对JavaScript中的类进行了扩展,为其添加了类型支持,如实现接口、泛型类等。定义一个类需要使用class关键字。类似于函数定义,类的定义也有以下两种方式: 类声明; 类表达式。
15.1.1 类声明
类声明的语法如下所示,class是关键字:
类名的首字母应该大写。示例:
此例中,我们声明了一个Circle类,它包含一个number类型的radius属性。使用new关键字能够创建类的实例。与函数声明不同的是,类声明不会被提升,因此必须先声明后使用。
在使用类声明时,不允许声明同名的类,否则将产生错误。
15.1.2 类表达式
在该语法中,class是关键字;Name表示引用了该类的变量名;ClassName表示类的名字。在类表达式中,类名ClassName是可选的。
如果在类表达式中定义了类名,则该类名只能够在类内部使用,在类外不允许引用该类名。
15.2 成员变量
或
除了在成员变量声明中设置初始值,我们还可以在类的构造函数中设置成员变量的初始值。示例如下:
虽然为类的成员变量设置初始值是可选的,但是对成员变量进行初始化是一个好的编程实践,它能够有效避免使用未初始化的值而引发的错误。因此,TypeScript提供了“–strictPropertyInitialization”编译选项来帮助严格检查未经初始化的成员变量。
需要注意的是,“–strictPropertyInitialization”编译选项必须与“–strictNullChecks”编译选项同时启用,否则“–strictPropertyInitialization”编译选项将不起作用。
若启用了“–strictPropertyInitialization”编译选项并且仅在构造函数中对成员变量进行了初始化操作,那么需要在构造函数中直接进行赋值操作。如果通过在构造函数中调用某个方法,进而在该方法中间接地初始化成员变量,那么编译器将无法检测到该初始化操作,因此会产生编译错误。
在一些场景中,我们确实想要通过调用某些方法来初始化类的成员变量。这时可以使用非空类型断言“!”来通知编译器该成员变量已经进行初始化,以此来避免产生编译错误。
在声明类的成员变量时,在成员变量名之前添加readonly修饰符能够将该成员变量声明为只读的。只读成员变量必须在声明时初始化或在构造函数里初始化。
15.3 成员函数
成员函数也称作方法,声明成员函数与在对象字面量中声明方法是类似的。
在成员函数中,需要使用this关键字来引用类的其他成员。
15.4 成员存取器
成员存取器由get和set方法构成,并且会在类中声明一个属性。成员存取器的定义方式与对象字面量中属性存取器的定义方式是完全相同的。 如果一个类属性同时定义了get方法和set方法,那么get方法的返回值类型必须与set方法的参数类型一致,否则将产生错误。如果一个类属性同时定义了get方法和set方法,那么get方法和set方法必须具有相同的可访问性。存取器是实现数据封装的一种方式,它提供了一层额外的访问控制。类可以将成员变量的访问权限制在类内部,在类外部通过存取器方法来间接地访问成员变量。在存取器方法中,还可以加入额外的访问控制等处理逻辑。
15.5 索引成员
类的索引成员会在类的类型中引入索引签名。索引签名包含两种,分别为字符串索引签名和数值索引签名。在实际应用中,定义类的索引成员并不常见。类中所有的属性和方法必须符合字符串索引签名定义的类型。同时,只有当类具有类似数组的行为时,数值索引签名才有意义。
类的索引成员与接口中的索引签名类型成员具有完全相同的语法和语义,这里不再重复。
在类的索引成员上不允许定义可访问性修饰符,如public和private等。
15.6 成员可访问性
成员可访问性定义了类的成员允许在何处被访问。TypeScript为类成员提供了以下三种可访问性修饰符:public;protected;private。
在JavaScript语言中不支持这三种可访问性修饰符。
15.6.1 public
类的公有成员没有访问限制,可以在当前类的内部、外部以及派生类的内部访问。类的公有成员使用public修饰符标识。默认情况下,类的所有成员都是公有成员。因此,在定义公有成员时也可以省略public修饰符。
15.6.2 protected
类的受保护成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。类的受保护成员使用protected修饰符标识。
15.6.3 private
类的私有成员只允许在当前类的内部被访问,在当前类的外部以及派生类的内部都不允许访问。类的私有成员使用private修饰符标识。
15.6.4 私有字段
在ECMAScript标准中,类的私有字段使用一种新的语法来定义,即在字段标识符前添加一个“#”符号。不论是在定义私有字段时还是在访问私有字段时,都需要在私有字段名前添加一个“#”符号。
15.7 构造函数
构造函数用于创建和初始化类的实例。当使用new运算符调用一个类时,类的构造函数就会被调用。构造函数以constructor作为函数名。
与普通函数相同,在构造函数中也可以定义可选参数、默认值参数和剩余参数。但是构造函数不允许定义返回值类型,因为构造函数的返回值类型永远为类的实例类型。
在构造函数上也可以使用可访问性修饰符。它描述的是在何处允许使用该类来创建实例对象。在默认情况下,构造函数是公有的。如果将构造函数设置成私有的,则只允许在类的内部创建该类的对象。
将没有函数体的构造函数声明称为构造函数重载,同时将定义了函数体的构造函数声明称为构造函数实现。
构造函数重载可以存在零个或多个,而构造函数实现只能存在一个。
15.8 参数成员
在构造函数参数列表中,为形式参数添加任何一个可访问性修饰符或者readonly修饰符,该形式参数就成了参数成员,进而会被声明为类的成员变量。
readonly修饰符也可以和任意一个可访问性修饰符结合使用来定义只读的参数成员。
15.9 继承
继承是面向对象程序设计的三个基本特征之一,TypeScript中的类也支持继承。在定义类时可以使用extends关键字来指定要继承的类class DerivedClass extends BaseClass { }
在该语法中,我们将BaseClass叫作基类,将DerivedClass叫作派生类,派生类继承了基类。有时候,我们也将基类称作父类,将派生类称作子类。
当派生类继承了基类后,就自动继承了基类的非私有成员。
15.9.1 重写基类成员
在派生类中可以重写基类的成员变量和成员函数。在重写成员变量和成员函数时,需要在派生类中定义与基类中同名的成员变量和成员函数。
在派生类中,可以通过super关键字来访问基类中的非私有成员。当派生类和基类中存在同名的非私有成员时,在派生类中只能通过super关键字来访问基类中的非私有成员,无法使用this关键字来引用基类中的非私有成员。
若派生类重写了基类中的受保护成员,则可以将该成员的可访问性设置为受保护的或公有的。也就是说,在派生类中只允许放宽基类成员的可访问性。
由于派生类是基类的子类型,因此在重写基类的成员时需要保证子类型兼容性。
15.9.2 派生类实例化
在派生类的构造函数中必须调用基类的构造函数,否则将不能正确地实例化派生类。在派生类的构造函数中使用“super()”语句就能够调用基类的构造函数。
在派生类的构造函数中,引用了this的语句必须放在“super()”调用的语句之后,否则将产生编译错误,因为在基类初始化之前访问类的成员可能会产生错误。
在实例化派生类时的初始化顺序如下:
1)初始化基类的属性。
2)调用基类的构造函数。
3)初始化派生类的属性。
4)调用派生类的构造函数。
15.9.3 单继承
TypeScript中的类仅支持单继承,不支持多继承。也就是说,在extends语句中只能指定一个基类。
15.9.4 接口继承类
TypeScript允许接口继承类。若接口继承了一个类,那么该接口会继承基类中所有成员的类型。
在接口继承类时,接口不但会继承基类的公有成员类型,还会继承基类的受保护成员类型和私有成员类型。如果接口从基类继承了非公有成员,那么该接口只能由基类或基类的子类来实现。
15.10 实现接口
虽然一个类只允许继承一个基类,但是可以实现一个或多个接口。在定义类时,使用implements语句能够声明类所实现的接口。当实现多个接口时,接口名之间使用逗号“,”分隔。
如果类的定义中声明了要实现的接口,那么这个类就需要实现接口中定义的类型成员。
15.11 静态成员
类的静态成员不属于类的某个实例,而是属于类本身。类的静态成员使用static关键字定义,并且只允许通过类名来访问。
15.11.1 静态成员可访问性类的public静态成员对访问没有限制,可以在当前类的内部、外部以及派生类的内部访问。
类的protected静态成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。
类的private静态成员只允许在当前类的内部访问。
15.11.2 继承静态成员
类的public静态成员和protected静态成员也可以被继承。
15.12 抽象类和抽象成员
定义抽象类时,只需要在class关键字之前添加abstract关键字即可。
抽象类与具体类的一个重要区别是,抽象类不能被实例化。也就是说,不允许使用new运算符来创建一个抽象类的实例。抽象类的作用是作为基类使用,派生类可以继承抽象类。
抽象类也可以继承其他抽象类。抽象类中允许(通常)包含抽象成员,也允许包含非抽象成员。 抽象成员抽象成员不允许包含具体实现代码。
如果一个具体类继承了抽象类,那么在具体的派生类中必须实现抽象类基类中的所有抽象成员。因此,抽象类中的抽象成员不能声明为private,否则将无法在派生类中实现该成员。若没有正确地在具体的派生类中实现抽象成员,将产生编译错误。
15.13 this类型
在类中存在一种特殊的this类型,它表示当前this值的类型。我们可以在类的非静态成员的类型注解中使用this类型。
this类型是动态的,表示当前this值的类型。当前this值的类型不一定是引用了this类型的那个类,该差别主要体现在类之间有继承关系的时候。
注意,this类型不允许应用于类的静态成员。
15.14 类类型
类声明将会引入一个新的命名类型,即与类同名的类类型。类类型表示类的实例类型,它由类的实例成员类型构成。
第6章 TypeScript类型进阶
本章主要内容: 带有类型参数的泛型。 具有块级作用域的局部类型。 常用的联合类型和交叉类型。 实用的索引类型、映射对象类型以及条件类型。 TypeScript内置的实用工具类型。 能够获取表达式类型的类型查询。 类型断言与类型细化。
暂时用不上,后续再细看。
第7章 TypeScript类型深入
本章主要内容: TypeScript中的两种兼容性,即子类型兼容性和赋值兼容性。 TypeScript中的类型推断功能以及类型放宽行为。 能够帮助组织代码的命名空间与模块。 TypeScript声明文件的书写与应用。 TypeScript模块解析流程。 TypeScript特有的声明合并功能。
暂时用不上,后续再细看。
第8章 TypeScript配置管理
本章主要内容:安装并使用TypeScript编译器来编译TypeScript程序。如何使用编译选项。使用“tsconfig.json”配置文件来管理TypeScript工程。如何对JavaScript代码进行类型检查。如何使用三斜线指令。
TypeScript编译器程序位于TypeScript语言安装目录下的lib文件夹中。TypeScript编译器对外提供了一个命令行工具用来编译TypeScript程序,它就是tsc命令。
如果待编译文件的文件名中带有空白字符,如空格,那么就需要使用转义符号“\”或者使用单、双引号将文件名包围起来。
TypeScript编译器提供了一种特殊的编译模式,即观察模式。在观察模式下,编译器会监视文件的修改并自动重新编译文件。观察模式通过“–watch”(简写为“-w”)编译选项来启用。
在观察模式下,编译器每次编译文件之前都会清空命令行窗口中的历史输出信息。如果我们想保留每一次编译的输出信息,则可以使用“–preserveWatchOutput”编译选项。
在TypeScript中,不论是长名字风格的编译选项还是短名字风格的编译选项均不区分大小写,即“–help”“–HELP”“-h”“-H”表示相同的含义。
到TypeScript官方网站上的“Compiler Options”页面中了解最新的编译选项列表。官方网站的Compiler Options页面地址:https://www.typescriptlang.org/docs/handbook/compiler-options.html 。
“tsconfig.json”配置文件是一个JSON格式的文件。若一个目录中存在“tsconfig.json”文件,那么该目录将被编译器视作TypeScript工程的根目录。
在启用了“–allowJs”编译选项后,编译器能够像编译TypeScript文件一样去编译JavaScript文件。
就算启用了“–allowJs”编译选项,编译器依然不会对JavaScript代码进行类型检查。
TypeScript 2.3提供了一个“–checkJs”编译选项。当启用了该编译选项时,编译器能够对“.js”和“.jsx”文件进行类型检查。“–checkJs”编译选项必须与“–allowJs”编译选项一起使用。
“// @ts-nocheck”是一个注释指令,如果为JavaScript文件添加该注释,那么相当于告诉编译器不对该JavaScript文件进行类型检查。此外,该指令也可以在TypeScript文件中使用。
如果一个JavaScript文件中添加了“// @ts-check”注释指令,那么编译器将对该Java-Script文件进行类型检查,不论是否启用了“–checkJs”编译选项。
“// @ts-ignore”注释指令的作用是忽略对某一行代码进行类型检查。
JSDoc是一款知名的为JavaScript代码添加文档注释的工具。
三斜线指令是一系列指令的统称,它是从TypeScript早期版本就开始支持的编译指令。目前,已经不推荐继续使用三斜线指令,因为可以使用模块来取代它的大部分功能。三斜线指令是以三条斜线开始,并包含一个XML标签。
第9章 TypeScript项目实践
本章主要内容:如何结合使用TypeScript和代码转译工具Babel。如何结合使用TypeScript和代码打包工具webpack。如何结合使用TypeScript和静态程序分析工具ESLint。如何在集成开发环境Visual Studio Code中开发TypeScript程序。
Babel的主要用途是将ES6及以上版本的JavaScript代码转译为兼容某一运行环境的JavaScript代码。
从Babel 7版本开始,Babel内置了对TypeScript语言的支持,因此也能够将TypeScript代码转译为兼容某一运行环境的JavaScript代码。
如果想要为程序添加静态类型检查的功能,则需要使用TypeScript。如果想要将Type-Script或JavaScript代码转译为兼容某一运行环境的JavaScript,则既可以使用TypeScript也可以使用Babel。
使用ESLint能够保证代码使用了一致的编码风格,从而增加了代码可读性。