简介


什么是TypeScript

TypeScript 是 JavaScript 的超集,其包含了 JavaScript 基本语法也具有额外扩展

TypeScript 是一个类型检查工具,给我们语法提示


强类型语言和弱类型语言

强类型语言和弱类型语言,是从类型安全的角度去判断的

强类型语言

强类型语言中不允许有任意的隐式类型转换

强类型语言

弱类型语言中允许任意的数据隐式类型转换


静态类型语言和动态类型语言

静态类型语言和动态类型语言,是从类型检查的角度去判断的

静态类型语言

变量声明时,它的类型是明确的,声明之后,变量的类型就不允许再修改

动态类型语言
  1. 在运行阶段才能明确变量类型,而且变量的类型随时可以改变。
  2. 也可以理解为,变量是没有类型的,变量中存放的值是有类型的

Flow


说明

  1. JavaScript 的类型检查器
  2. 通过给变量添加注解的方式,给变量标注是什么类型,通过注解在开发阶段检查变量类型是否存在异常
  3. 只是在编码阶段进行使用,运行阶段需要删除注解,否则无法运行
1
2
3
4
5
6
7

function sum (a:number,b:number) { // :number 就是类型注解
return a+b
}

sum(100,"100") // 如果传入类型不是 number 保存时就会报错

添加类型注解

  1. npm 安装 npm i flow-bin -D
  2. 在需要使用 flow 进行检查的文件中添加注释标记 //@flow
  3. 给变量添加类型注解
  4. 在保存时会自动进行类型检查

index.js 文件

1
2
3
4
5
6
7
8
//@flow    // 添加注释标记

function sum (a:number,b:number) { // 添加类型注释
return a+b
}

sum(100,100)
sum("100","100")

编译

说明

编译的目的是为了移除类型注解,因为类型注解只在代码编写过程中用来检查类型,代码运行是无法识别类型注解的,所以需要移除

移除方式

  1. flow-remove-types
    1. npm 安装 npm i flow-remove-types -D
    2. 运行 npm run flow-remove-types 指定目录a -D 指定目录b 将指定目录a下的文件编译到指定目录b
  2. babel
    1. 通过 babel,并添加 preset-flow 插件进行编译

vscode flow插件

可以直接在代码中直接显示类型异常,不需要看控制台

插件名 Flow Language Support


类型推断

变量没有使用类型注解,但是在代码编写中变量的使用可能进行隐式类型转换,此时 flow 也会提示错误


类型注解

变量

在变量后面使用 :type 的形式进行类型注解

1
2
3
4
5
let a:number = 12

function sum (a:number,b:number){
return a+b
}
函数
  1. 在函数体之前使用 :type 定义类型注解,用于定义函数返回值类型
  2. 函数没有返回值,类型注解定义为 :void
1
2
3
function sum (a,b):void {
return a+b
}

注解类型

flow官网类型文档:https://flow.org/en/docs/types
flow第三方类型手册:https://www.saltycrane.com/cheat-sheets/flow-type/latest/

基本类型

1
2
3
4
5
6
let a:string = "aaa"
let b:number = infinity // NaN // 100
let c:boolean = true // false
let d:null = null
let e:void = undefined
let f:symbol = Symbol()

数组

1
2
3
4
5
let a:Array<number> = [1,2,3]   // 通过泛型规定变量的类型是数组并且各个元素类型都为numberr
let b:number[] = [1,2,3]

// 元组
let c:[string,number] = ["aa",12] // 通过元组方式规定数组元素长度和元素各类型

对象类型

1
2
3
4
5
6
7
let a:{for:string,bar:number} = {for:"aa",bar:12}   // 规定变量类型为对象,并且必须有 for,bar 属性,且属性的类型分别为 string,number
let a:{for?:string,bar:number} = {for:"aa",bar:12} // 规定变量类型为对象,注解添加问号?,传入此变量的对象中此属性可有可无

let c:{[string]:string} = {}
c.key1 = "value1"
c.key2 = "value2"
// 通过键值对形式限制变量类型,给变量指向的对象添加属性时规定属性的键的类型和值的类型

函数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function sum (a:number,b:number) { // 规定函数参数类型
return a+b
}

function sum (a,b):number { // 规定函数返回值类型
return a+b
}

// 函数作为回调
function sum (cal:(string,number)=>void){ // 函数作为回调,规定传入回调的参数类型和回调的返回值类型
cal("aa",100)
}

特殊类型

字面量类型

限制变量必须接收某一个固定值

1
let a:"foo" = "foo" // 赋值必须是 "foo" 字符串,不能传入其他字符串或其他值,否则检查报错
联合类型

限制变量只能接收多种类型或字面量类型中的某个类型或某个值

1
2
let a:"foo"|"abb":"bcc" = "bcc"     // 只能接收 "foo" "abb" "bcc" 字符串中某个字符串
let b:number|string = 10 // 只能接收类型是 number 或 string 的值
类型别名

给注解的类型定义为一个类型变量,方便调用和配置

1
2
type abc = number|string    // 定义类型别名(类型变量)
let a:abc = 10 // 设置类型注解
maybe类型

使变量除了可以赋值类型注解规定的类型值,也可以赋值 nullundefined

1
let a:?number = null    // 通过 ? 使变量也可以赋值 null 和 undefined。等同于 let a:number|null|void = null
mixed类型 和 any类型
  1. mixed 和 any 都规定变量可以接收任意类型的数据
  2. mixed:属于强类型控制,当编写代码中使用 mixed 注解变量,调用此变量进行可能造成隐式类型转换的操作都将报错
  3. any:属于弱类型控制,编写代码中不会判断是否可能造成隐式类型转换。效果等同于使用 JavaScript 直接定义变量,存在意义也只是为了兼容性
1
2
3
4
5
6
7
8
9
10
11
12
function sum (a:mixed) {    // 注解变量可以接收任意类型数据
a.substr(1) // substr 只能处理字符串,如果是其他类型会先进行隐式类型转换,因为可能存在隐式类型转换的情况,因此 flow 在编码过程中就会报错
a+"100" // 同理
if(typeof a == "string"){ // 这样将不会报错
a.substr(1)
}
}

function sum(a:any){ // 注解变量可以接收任意类型数据
a.substr(1) // 不会有任何报错提示
}


flow 运行环境 API

在不同运行环境下,flow 有不同的对应 API,如在浏览器环境下,flow 就有 Element 类型等


TypeScript


安装

通过 npm 进行安装,可以安装到全局环境也可以安装到开发环境。安装到开发环境的好处是安装好依赖可以直接运行

1
2
3
4
npm i typescript -g     // 安装到全局环境
npm i typeScript -D // 安装到开发环境

tsc -V // 查看typescript版本

编译


编译流程

  • 使用 tsc 工具进行编译
    1. 先进行语法类型检查
    2. 然后移除类型注解
    3. 将 ES 新特性转换为 ES5 语法

编译指定文件

1
npm run tsc index.ts

编译整个项目

  1. 通过根目录下配置文件 tscconfig.json 编译整个项目
  2. 生成配置文件 tsc --init
  3. 直接执行 tsc 命令进行编译 npm run tsc tsc
tscconfig.json 配置项说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"target":"es5"              // 设置编译后的 JavaScript 所使用的 ES 标准
"module":"commonjs" // 输出代码使用什么方式进行模块化
"outDir":"./" // 编译后文件输出的文件夹
"rootDir":"./" // 设置源代码 .ts 文件所在的文件夹。默认都设置为 src 目录
"lib":["ES2015","DOM"] // 配置类型检查的标准库
"moduleResolution":"node", // 指定模块解析策略。TypeScript如何找到模块的导入或引用的过程
"skipLibCheck":true // 是否跳过 node_module 目录的检查

"sourceMap":true // 开启源代码映射
// 源代码映射的作用
// 1. 会在生成的 .js 文件旁生成一个 .js.map 的文件
// 2. 其作用是让调试器和其他工具在使用转换后的 .js 文件,仍然能定位到源代码 .ts 文件的代码位置,方便开发者在生产环境定位和解决bug

"strict":true // 开启严格检查选项

中文错误提示

1
npm run tsc --locale zh-CN  // 使用 tsc 编译文件,携带参数 --locale 就可以中文显示错误提示

类型

所有的类型注解,在运行阶段约束都将没有意义,编译后都将成为原始 JavaScript 代码


原始类型

1
2
3
4
5
6
7
8
9
let a:string = "foor"
let b:number = 100 | NaN | Infinity
let c:boolean = true | false
let d:void = null | undefined // "strict":true void 只能赋值 undefined
let e:null = null
let f:undefined = undefined

// TypeScript 给变量定义了类型注解,依旧允许赋值 null 和 undefined
// 前提是 "strict" 配置项设置为 false,否则报错

联合类型

用于设置变量可以接收多种类型中某种类型数据

1
let a:string | number = "foo"

标准库

说明

标准库就是 JavaScript运行环境 内置对象所对应的声明,如果没有配置相应的标准库,TypeScript 就无法识别新的内置对象类型。
如:Promise 是 ES6 新的内置对象,TypeScript 默认使用的是 ES5 和 其他标准库,所以在进行类型检测时并不理解 Promise 这种类型,因此会报错。

解决
  1. 设置 "target":"es5""target":"ES2015",即编译后的 js 使用 es6 标准,此时就可识别新的内置对象类型 Promise 等
  2. 配置标准库
    1. "target":"es5" 保持不变,依旧编译为 es5 标准的 js 文件
    2. 配置 "lib":["ES2015","DOM"] 配置项,添加 es6标准库浏览器环境标准库
    3. 配置了 lib,类型检查就只会在此配置项中配置的标准库去查找

object 类型

object 类型指非原始类型,对象、数组、函数

1
2
let foo:object = function(){...}        // 可以接收 对象、数组、函数
let a:{for:string,bar:number} = {for:"aa",bar:12} // 通过对象字面量的方式规定变量接收的数据为对象,并且对象只能有 for,bar 属性,且属性的类型分别为 string,number

数组类型

1
2
let a:Array<number> = [1,2,3]   // 使用泛型规定变量只能接收元素是数字的数组
let b:number[] = [1,2,3] // 规定变量只能接收元素是数字的数组

元组类型

什么是元组

明确数组元素数量、明确数组元素类型的注解类型

1
2
3
let a:[number,string] = [18,"xx"]       // 规定变量只能接收具有两个元素,且元素类型分别是数字、字符串,的数组
a = [18,"xx",15] // 会报错,超出两个元素
a = [18,19] // 会报错,两个元素类型应该分别是数字、字符串

枚举类型

什么是枚举

规定变量接收的值只能在一个数据范围中进行获取

特点
  1. 可以不指定枚举的值,不指定的话枚举的值从0开始累加
  2. 只指定第一个值为某数字,则后面没指定的值在此基础上累加
  3. 枚举的值为字符串,则枚举所有值都要设置初始值
  4. 枚举在编译之后不会被移除,而是编译为一个对象(双向键值对对象),使得可以用索引值获取枚举名称。p[0]
  5. 不通过索引获取枚举名,并且希望编译后移除枚举对象,则应该使用 const const enum p {...} 定义枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum p {        // 定义一个叫 p 的枚举
a = 0, // 用 = 和 , 定义枚举的值
b = 1,
c = 2
}

enum q { // 定义一个叫 p 的枚举
a, // 不指定枚举的值,a 默认为0
b, // b = 1
c // c = 2
}

enum r { // 定义一个叫 p 的枚举
a = 6, // a = 6
b, // b = 7
c // c = 8
}

let obj = {
name:"xx",
status:p.a // 用法和使用对象方式相同
}

函数类型

函数声明
  1. (a:number,b:string=10,c?:boolean,...rest:number[])=>string 此整体表示一个函数类型
  2. 形参 添加 类型注解,规定参数可以接收的数据类型,实参的 参数类型参数个数 必须保持一致
  3. ( ) 后添加类型注解,规定函数的 返回值类型
  4. 参数设置为 可选参数,形参后面加 ? ,可选参数放在最后
  5. 默认值参数 在形参后用 = 直接赋值,默认值参数放在最后
  6. 接收 任意数量参传,使用es6 ... 操作符,可以给任意数量参数添加数组类型注解,因为 任意数量参数 是个数组
  7. 函数第一个参数定义为 this:void,此参数表示 this,他是个伪参数,编译后不会有此形参
1
2
3
function fn (a:number,b:string=10,c?:boolean,...rest:number[]):string{
return "foo"
}
函数表达式
  1. 作为函数调用
    1. 函数表达式 值的部分 与函数声明相关的类型注解相同
    2. 函数表达式 变量部分 可以添加类型注解
  2. 作为回调函数
    1. 变量类型必须是 函数类型,并且可注解形参和返回值类型,规定调用时接收实参的类型
1
2
3
4
5

let fn:(a:number,b:string) => string = function (a:number,b:number):string {
return "foo"
}


任意类型

效果等同于 JavaScript 变量赋值,可以接收任意类型的数据,且在运行过程中可以接收其他类型值
作用是为了兼容性,不建议使用

1
2
let a:any = 12
a = "foo"

隐式类型推断

  1. 如果变量没有定义类型注解,TypeScript 会根据代码推断其类型
  2. 被推断的了类型变量将不可以再接收其他类型值
  3. 如果无法推断类型,则将标记为 any
1
2
3
4
5
6
let a = 18      // 隐式类型推断为 number
a = "foo" // 报错

let b // 隐式类型推断为 any
b = 123
b = "foo"

类型断言

  1. TypeScript 无法推断变量的类型,将其定义为 any 类型,如果后续代码相关操作可能会造成隐式类型转换,则会 TypeScript 会报错
  2. 为了解决这个问题,需要 明确告诉 TypeScript 以某种类型进行操作
1
2
3
4
5
6
let a = [100,190,232,180]
let b = a.find(i => i>0) // 隐式类型推断 b 为 number 或 undefined 。因为觉得有可能找不到
b * b // 有可能会进行隐式类型转换,因此报错

let c1 = b as number // 断言:语法一
let c2 = <number>b // 断言:语法二

接口


说明和基础用法

  1. 是一种规范,用来约束对象的结构
    1. 属性名
    2. 属性类型
  2. 其他的类型都是约束基本类型的数据,接口是约束对象的
  3. 和其他类型注解一样,在运行阶段约束都将没有意义
1
2
3
4
5
6
7
8
9
10
interface p {           // 定义接口
title:string // 属性定义可以用 ; 或 , 分割也可以用换行分割
age:number
}

function fn (post:p) {
console.log(p.title)
console.log(p.age)
console.log(p.name) // 报错,接口中没有 name 成员
}

类型扩展

  1. 可选成员:title?:string
  2. 只读成员:title:undefined
  3. 动态成员:[foo:string]:string
1
2
3
4
5
6
7
8
9
10
interface p {
title?:string // 使用 ? 定义【可选成员】,即属性类型为 string 或 undefined
readonly name:string // 使用 readonly 定义 【只读成员】,初始化后的属性值将不可以再被修改
}

let a:p = {
title:undefined, // 【可选成员】约束,所以可以赋值 undefined
name:"aaa"
}
a.name = "bbb" //报错 // 【只读成员】约束,所以初始化后不可再修改属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
// 当不明确对象会有哪些属性需要使用【动态成员】进行约束

interface p {
[foo:string]:string
}
// foo 任意字符,代表所有属性名称
// string(1) 约定属性键的类型(对象中属性键可以是数字,也可以是字符串,因此可以约定类型)
// string(2) 约定属性值的类型

let a:p = {}
a.foo = "aaa"
a[2] = "bbb" // 报错,键的类型不符合约束
a.stt = 123 // 报错,值的类型不符合约束


基本使用

概念
  1. 生活中:描述一类具体事物的抽象特征
  2. 代码中:描述一类具体对象的抽象成员(属性)
说明
  1. ES6 之前使用 函数 + 原型 实现类
  2. ES6 有 clss 语法糖实现类
  3. TypeScript 增强 class 相关语法
1
2
3
4
5
6
7
8
9
10
11
12
13
class P {       // 定义类 P
name:string = "foo" // ES7 中需要先在类中定义成员,否则构造函数无法初始化。定义成员时可以初始化,也可以在构造函数中进行初始化
age:number // 给成员添加类型注解

constructor (name:string,age:number) { // 给构造函数的形参添加类型注解
this.name = name // ES7 没有在类中定义成员,则无法初始化
this.age = age
}

fn(msg:string):void{ // 对象方法形参和返回值添加类型注解
console.log( `I am ${this.name}, ${msg}` )
}
}

类成员的访问修饰符

说明
  1. 成员:属性、方法
  2. 用于控制类中成员的可访问范围
访问修饰符
  1. 公有成员 public:允许在任何地方访问成员【类中通过 this 调用 或 类外通过对象调用】。成员不添加任何修饰符,默认就是 public
  2. 私有成员 private:只允许在同一个类中访问【类中通过 this 调用】
  3. 受保护的成员 protected:允许在同一个类和子类中访问【类中通过 this 调用】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class P {
public name:string // 共有成员
private age:number // 私有成员
protected msg:string // 受保护成员

constructor (name:string,age:number,msg:string) {
this.name = name
this.age = age
this.msg = msg
}

fn(msg:string):void{
console.log( this.age ) // 私有成员只能在类中通过 this 调用
}
}

let pp = new P()
pp.name // 公有成员可以在任意地方调用

class q extends p {
constructor (name:string,age:number,msg:string) {
this.super(name,age,msg) // 调用父类构造函数
console.log(this.msg) // 受保护的成员可以在本类和子类中被访问
}
}
特例

如果构造函数被定义为私有成员,则需要在类中声明一个静态函数,然后在静态函数中调用构造函数并返回实例对象(调用构造函数就是使用 new)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class P {
public name:string // 共有成员
private age:number // 私有成员
protected msg:string // 受保护成员

private constructor (name:string,age:number,msg:string) { // 构造函数定义为私有成员
this.name = name
this.age = age
this.msg = msg
}

static fn () { // 定义静态函数
return new P() // 再静态方法中调用私有构造函数
}
}

let pp = P.fn() // 获取实例对象

只读属性

  1. 定义属性是只读的,可以初始化属性值,但是不允许对初始化的属性值进行修改
  2. 只读标识符 readonly 必须放在属性访问修饰符之后
1
2
3
4
5
6
7
8
9
10
11
class P {
public name:string
private readonly age:number = 12 // 定义私有属性并设置为只读,且进行初始化(也可以在构造函数进行初始化)
protected msg:string

private constructor (name:string,age:number,msg:string) {
this.name = name
this.age = age // 报错,不允许修改对只读属性的值(已经在声明成员时初始化了)
this.msg = msg
}
}

类和接口

  1. 接口约束了类实现对象的结构
  2. 从现实中理解是抽离出公有特性进行约定
  3. 只是进行约定,没有具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Name_I {                  // 定义接口约束类创建的对象结构
name (fristname:string):void
}
interface Age_I { // 非强制性的规范,一个接口对应一个约束
age (year:number):void
}

class P implements Name_I,Age_I{ // 类实现了多个接口
name (fristname:string):void {
return fristname
}
age (year:number):void{
return year
}
}
class Q implements Name_I,Age_I{
name (fristname:string):void {
return fristname
}
age (year:number):void{
return year
}
}

抽象类

  1. 抽象类可以包含属性或方法的实现,但是接口只能约定不能实现
  2. 抽象类中可以定义抽象方法或抽象属性,必须要子类进行实现。效果与接口相同
  3. 类只能继承一个抽象类,但是可以实现多个接口
  4. 抽象类在运行时存在,可以用 instanceof 检查,接口只在编译时存在
1
2
3
4
5
6
7
8
9
10
11
12
abstract clss Name {                    // 定义抽象类
name (fristname:string):void { // 可以定义方法的实现
return fristname
}
abstract age (year:number):void // 抽象方法不能被实现,只能由子类实现
}

class P extends Name {
age (year:number):void{ // 子类实现抽象方法
return year
}
}

泛型

  1. 函数、类、接 口中的类型变量,以便在调用时指定具体的类型
  2. 调用时通过 < > 给类型变量赋指定类型
1
2
3
4
5
function p <T> (length:number,value:T):T[]{     // 通过 <T> 给函数定义泛型。函数其他变量的类型注解调用这个类型变量
let arr = Array<T>(length).fill(value)
return arr
}
let res = p<string>(3,"foo") // 函数调用时才指定具体类型

类型声明

当使用第三方模块,但不是使用 TypeScript 编写,无法在 TypeScript 中进行类型检查,因此需要进行类型声明

  1. 使用 declare 对属性进行类型声明
  2. 下载对应的类型声明模块
  3. 有的模块自带类型声明文件,不需要其他操作
1
2
import { camelCase } from 'lodash'                  // lodash 模块没有类型声明文件
declare function camelCase (input:string):string // 手动进行类型声明