优缺点
优点
缺点
基础类型
布尔 boolean
```ts
let isFlag: boolean = true
```
数值 number
```ts
let decLiteral: number = 20 // 十进制
let hexLiteral: number = 0x14 // 十六进制
let binaryLiteral: number = 0b10100 // 二进制
let octalLiteral: number = 0o24 // 八进制
let goldenSection: number = 0.618 // 浮点数
let notANumber: number = NaN // 非数字
```
字符串 string
```ts
let name: string = 'helen'
let age: number = 30
let sentence = `Hello, my name is ${name}. I'll be ${age + 1}year old next month`
```
void
当一个函数没有返回值时,可以将其返回值类型定义为 void:
```ts
function doNothing: void() {}
```
`注`:声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null
null 和 undefined
`undefined` 和 `null` 是所有类型的子类型,意思其他类型的变量都能被赋值`undefined`和`null`。
bigint
symbol
数组 array
有以下两种声明方式:
- 数组元素类型[],推荐使用这种方式
- Array<数组元素类型>,也称为数组泛型
let list1: number[] = [1, 2, 3] // 推荐使用
let list2: Array<number> = [1, 2, 3] // 数组泛型
元祖 tuple
存储不同类型的元素,而非像数组那样只能存储相同元素类型(any[] 除外)。
相同类型元素组成成为数组,不同类型元素组成了元组(Tuple)。
let x: [string, number] = ['hello', 10]
元组的类型对应的值必须符合所定义的类型。
枚举 enum
定义一组带有名字的常量。
数字枚举
- 如果不对枚举常量赋值,则枚举元素中的值类型默认为
数字类型
,默认从0开始递增。 - 如果枚举元素中第一个值不为0的数字,则其他值从该数值开始递增
- 如果枚举元素的某个元素被赋予数值,则该元素往后的值从该元素值开始递增
- 数字枚举成员的值可以是表达式,但是其紧接的成员值必须被初始化。
enum Color {
Red,
Blue = 2,
Green,
Yellow = getSomeValue(),
White = 5 // 这里不初始化则会报错
}
Color.Red === 0 // true
Color.Blue === 2 // true
COlor.Green === 3 // true
字符串枚举
指将所有的枚举成员的用字符串进行初始化。
异构枚举 Heterogeneous enums
枚举成员的值包含 number 类型和 string 类型。不建议使用。
object VS enum
const enum EDirection {
Up,
Down,
Left,
Right
}
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3
} as const
任意 any
在不能确定变量类型的情况下,我们不希望类型检查器对这些值进行检查,而是直接让它们通过编译阶段的检查,此时可以使用 any。
- any 类型的变量可以被赋予任何类型的值
- any 类型的变量类似object对象,可以以对象属性的方式访问其存在或不存在的属性
- any 类型的变量无需事先执行任何类型的检查
注
:不建议使用这种数据类型,不便于维护代码。
对象 object
object
类型表示非原始类型,数组、元素、枚举和普通对象都是object
类型。
let obj: object
enum gender {
male = 1,
female = 2
}
obj = gender
obj = [1, 2, 3]
obj = ['a', 1]
obj = { name: 'lily'}
never 和 unknown
-
never
类型表示那些永不存在的值的类型。never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never。
使用场景:
- 一个抛出异常的函数表达式,其函数返回值类型为 never
- 不会有返回值的函数表达式,其函数返回值类型也为 never
- 不能取得值的地方
-
unknown
类型是any
类型对应的安全类型。- unknown 类型的变量可以被赋予其他类型的值
- unknown 类型的变量只能分配给 any 类型和 unknown 类型本身
注
:在那些将取得任意值,但不知道具体类型的地方使用 unknown,而非 any。
类 class
new 操作符会创建一个对象,并调用 contructor 构造函数初始化这个对象。
特性
继承 extends
使用继承来扩展类,使用关键字 extends
实现继承,被继承的类也叫做 基类或超类
,继承的类叫做 派生类或子类
。
class Animal {
move(distance: number = 0) {}
}
class Dog extends Animal {
bark() {
console.log('Woof')
}
}
const dog = new Dog()
dog.bark()
dog.move(10)
如果在子类
中包含了一个 constructor
,则这个子类在 constructor
中使用this
之前必须调用 super()
,相当于调用了超类的 constructor
,这是ES6中一条强制执行的规则,在TS同样也是强制执行的一条规则。
公共访问修饰符 public
在TS中,类中的所有成员默认都是 public
,可以自由地访问这些 public
成员。
class Animal {
public name: string
public constructor(name: string) {
this.name = name
}
public move(distance: number = 0) {
console.log(`${this.name} moved ${distance}m`)
}
}
私有访问修饰符 private
private
修饰的成员有几个特点:
- 只能在声明它的类内部使用
- 不能被继承
- 实例对象也不能访问到
- 可以通过实例方法来间接访问
private
成员
class Animal {
private name: string
construcotr(name: string) {
this.name = name
}
getName() {
return this.name
}
}
class Dog extends {
constructor(name: string) {
super(name)
}
}
const cat = new Animal('cat')
const dog = new Dog('旺财')
console.log(cat.name) // 提示错误,name仅能在 Animal 内部使用
console.log(cat.getName()) // cat
console.log(dog.name) // 提示错误,name仅能在 Animal 内部使用
console.log(dog.getName()) // 旺财
保护访问修饰符 protected
protected
修饰的成员和 private
的区别是,protected
修饰的成员可以在子类中可以访问。
特点:
protected
成员不可以在类的外部访问,但是可以被继承,即可以在子类中访问。protected
修饰的构造函数,表明该类不可以被实例化,但是可以被继承。- 可以通过实例方法来间接访问该成员
只读修饰符 readonly
只读属性只能在声明时或构造函数中被初始化,一但初始化就不能再修改。只读除了初始化和不能被修改之外,其余性质和其他成员一样可以被继承被访问到。
参数属性
定义:通过给构造函数的参数前面添加一个访问限定符来声明一个属性,并且这个成员会被这个访问限定符修饰。
class Animal {
constructor(readonly name: string) {
}
}
class Animal {
constructor(private name: string) {
}
}
存取器 getters/setters
通过 getters/setters
来截取对对象成员的访问,有效控制对对象成员的访问。
let password = '123456'
class Employee {
private _fullName: string
get fullName() string {
return this._fullName
}
set fullName(newName: string) {
if (password && password === '123456') {
this._fullName = newName
} else {
console.error('Error: 密码错误')
}
}
}
let employee = new Employee()
employee.fullName = '章三'
特点:
- 要求编译器设置输出 ES5+
- 只有 get,没有 set 的存取器自动被推断为
readonly
类的静态成员 static
特点:
- 是类成员,只能通过类名访问,在类内部也要通过类名来访问
- 不能被继承
抽象类 abstract
有抽象类、抽象方法。
- 与接口不同的是,抽象类内部可以包含成员的实现细节。
- 抽象类中的抽象方法不包含具体实现,并且必须在子类中实现
高级技巧
- 声明一个类时,同时创建了该类实例对象的类型,即该类
把类当作接口使用
类定义会创建:
- 类的实例类型
- 构造函数
可以在定义接口的时候,继承该类,把这个类当作接口一样使用。
函数
函数有两种类型:
- 命名函数
function add(num1: number, num2: number): number { return num1 + num2 }
- 匿名函数
let add: (num1: number, num2: number) => number = function(num1, num2) { return num1 + num2 }
函数类型
函数类型包含2个部分:
- 参数类型
- 返回值类型
ts中完整的函数形式是包含参数类型和返回值类型:
let myAdd: (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
推断类型
在赋值语句左边指定了类型,右边没有指定类型会自动推断出参数类型和返回值类型。
可选参数和默认参数
TS 默认情况是要求函数参数都是必须的,即函数调用时必须为每个参数都传入值。
JS 中参数是可选,如果不传入对应参数的值,则该参数值默认是 undefined
。
可选参数 ?
TS 中使用 ?
跟在参数名后名,表示该参数是可选的,可选参数必须在所有
必须参数的后面。
function getFullName(firstName: string, lastName?: string) {
return firstName + lastName
}
getFullName('Jhon') // 正确
getFullName('Jhon', 'Smith') // 正确
默认参数
function getFullName(firstName: string, lastName = 'Smith') {
return firstName + lastName
}
getFullName('Jhon') // 正确
getFullName('Jhon', 'Smith') // 正确
特点:
-
默认参数不需要放在必须参数后面
这样的默认参数在使用时可传入
undefined
使其默认值生效。 -
所有
必须参数后面的默认参数都是可选的也就是说可选参数与末尾的默认参数共享参数类型。
function getFullName(firstName: string, lastName?: string) { // } function getFullName(firstName: string, lastName = 'Smith') { // }
上述两个函数共享同一个函数类型:
(firstName: string, lastName?: string) => string
剩余参数 ...rest
必须参数、默认参数和可选参数都表示的是某一个
参数。
JS 中使用 arguments
来获取所有参数,也可以使用剩余变量...rest
来获取剩余参数列表,其中rest
表示的是剩余参数变量。
在TS中,同样可以使用剩余参数变量获取剩余的参数。
特点:
- 表示:
...rest
,其中rest
是剩余参数变量名,可以是任意的名字。 - 剩余参数变量是一个数组类型
function getFullName(firstName: string, ...restOfName: string[]): string {
return firstName + restOfName.jon(' ')
}
// 函数类型
let myFullName: (firstName: string, ...resetOfName: string[]) => string = getFullName
this
JS 中,this
只有在函数被调用时才指定。
ES6中,使用箭头语法来创建函数时,this
在函数创建时就指定了,而不是在调用时指定。
重载
JS 中函数会根据不同的参数类型来返回不同的值。
TS 中为同一个函数提供不同的函数类型定义来进行函数重载。
function pickCard(x: {suit: string; card: number; }[]): number
function pickCard(x: number): {suit: string; card: number; }
function pickCard(x): any {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
重载函数的调用过程:
查找重载列表,尝试使用第一个重载定义,如果匹配则使用,否则继续查找下一个定义。因此要把参数最具体的放在最前面。
pickCard(x): any
不是重载列表的一部分。上面的pickCard
函数的重载列表只有上面的两个。
类型推论
类型推断发生的时间:
- 初始化变量和成员时
- 函数的参数设置默认值和返回值时
通用类型
计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。
两种情况:
-
候选类型共享相同的通用类型,但是却没有一个类型能做为所有候选类型的类型。
这时需要将这个共享的通用类型指定为这个变量的类型。
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()]
-
不能为所有候选类型找到共享相同的通用类型,则候选类型的联合数组类型。
type Animal = Rhino | Elephant | Snake let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()]
上下文类型
发生位置:表达式的类型与所处的位置相关时。
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button) // 报错
}
TS 类型检查器使用winsow.onmousedown
来推断右边函数表达式的类型,能够推断出 mouseEvent
的类型。
- 如果函数表达式不是在上下文的位置,则需要讲参数类型指定为
any
。 - 如果函数表达式包含了明确的类型信息,则上下文类型被忽略。
使用场景:
- 函数参数
- 赋值表达式的右边
- 类型断言
- 对象成员
- 数组字面量
- 返回值语句
类型兼容性
TS 里的类型兼容是基于结构子类型的。
了解以下几个概念:
- 名义类型:通过明确的声明和类型的名称来决定的。
- 结构类型:是一种只使用成员来描述类型的方式,不要求明确声明。
- 超集 superset,js中的父类(也称超类、基类)
- 子集 subset,js中的子类(也称派生类)
要了解 typescript 中的类型兼容性,要从以下几个方面学习:
- 协变 covariant:子集可以赋值给超集
- 父类 = 子类(正确)
- 子类 = 父类(错误)
- 逆变 contravariant,只针对函数参数有效,
--strictFunctionTypes
开启时,只支持逆变- 父类 = 子类(错误)
- 子类 = 父类(正确)
- 双向协变 bivariant,只针对函数参数有效,
--strictFunctionTypes
关闭时,支持双向协变- 父类 = 子类(正确)
- 子类 = 父类(正确)
- 不变 invariant
对象/类兼容性(协变 convariant
)
子类类型 赋值 给父类类型,因为可以访问到共同的成员。
反过来的话,父类类型 赋值给 子类类型,由于父类中没有子类中特有的成员,当访问子类中特有的成员时就会报错,所以 父类类型不可以赋值给子类类型
class Animal {
doAnimalThing() {
console.log('do animal thing')
}
}
class Cat extends Animal {
doCatThing() {
console.log('喵喵')
}
}
function foo(animal: Animal) {
animal.doAnimalThing()
}
foo(new Animal()) // ok
foo(new Cat()) // ok 子类类型 赋值给 父类类型
function bar(animal: Cat) {
animal.doCatThing()
}
bar(new Cat()) // ok
bar(new Animal()) // Error,Animal类型中没有doCatThing这个方法
Cat类型继承了Animal类型的 doAnimalThing方法,所以传入 Cat类型的对象时能够正确执行。
interface Named {
name: string
}
let y = { name: '章三', location: '中国' }
let x: Named
x = y // ok
在对象兼容性赋值操作时,只有目标类型的成员会被一一检查是否都在源类型中。
这个比较的过程时递归进行的,检查每个成员及其子成员。
函数兼容性
函数参数兼容性(逆变和双向协变 contravariant
和bivariant
)
-
逆变
contravariant
let x = (a: number) => 0 let y = {b: number, s: string) => 0 y = x // ok x = y // error
-
根据JS中函数参数可以省略的规则,参数列表的源类型成员必须要在参数列表的目标类型中找到对应的参数,成员名字可以不同,成员类型必须相同,即源类型成员可以比目标类型成员个数少。
-
第二个例子中,源类型类型有一个必须成员,而目标类型中没有该成员,不可以赋值,相当于函数调用时参数可以少,但是不能多。
-
-
双向协变
bivariant
开启
strictFunctionTypes
,会禁用函数参数的双向协变。
函数返回值兼容性(协变 covariant
)
- 参考对象或原始类型的兼容性赋值
- 源函数的返回值类型必须是目标函数返回值类型的子类型
枚举兼容性
- 数字枚举类型和数字类型兼容
- 不同枚举类型之间不兼容
// 数字枚举
enum Color {
Red,
Blue
}
// 字符串枚举
enum WeekDay {
Monday = 'monday',
Theusday = 'theusday'
}
const c: Color = 1 // ok
let w: WeekDay
w = 1 // error
w = 'monday' // error
w = Color.Red // error
w = WeekDay.Monday // ok
类兼容性
参考对象类型兼容性(协变),不过类只比较类的实例部分,静态成员和构造函数不参与比较。
泛性兼容性
比较之前需要指定泛型参数,否则泛型参数默认为 any
类型。
高级类型
交叉类型(Intersection Types) &
含义:使用
&
符号将多个类型合并为一个类型,新的类型包含了合并前的所有类型
使用这种类型要都是交叉类型中子类型的类型。
联合类型(Union Types) |
interface
vs type
类型别名 Type Aliases
为
任意类型
起一个名字
- 主要是为一个类型起另外一个名字来使用。
type stringAliases = string // 为 string 类型另外取了一个名字,使用时还是 string 类型。
接口 intereface
接口声明是命名对象类型的一种方式。
相同点和区别
相同点:
- 都可以进行扩展
- 接口使用
extends
进行扩展
interface Animal { name: string } interface Bear extends Animal { honey: boolean } const bear = getBear() bear.name // ok bear.honey // ok
- 类型别名使用
&
来进行扩展
type Animal { name: string } type Bear = Animal & { honey: boolean } const bear = getBear() bear.name // ok bear.honey // ok
- 接口使用
区别:
- 接口可以通过声明同名接口的方式来合并接口,达到为已有接口添加成员的目的。
interface Window { title: string } interface Window { ts: TypeScriptAPI }
- 类型别名不可声明同名的类型别名
类型断言
你比 typescript 更确定是具体什么类型时可以使用类型断言来进行断言某个变量或参数时什么类型。
类型有两种表示方式:
- 使用
as
const myCanvas = document.getElementById('canvas') as HTMLCanvasElement
- 使用
<type>variable
const myCanvas = <HTMLCanvasElement>document.getElementById('canvas')
这个例子中,ts 只知道会返回 HTMLElement
,但是我们可以确定返回的是 HTMLCanvasElement
,只有这样我们才能使用这个类型特有的方法 getContext('2d')
.
注意:
-
和类型注解一样,类型断言会在编译阶段被移除,不会对运行时代码造成影响。
-
类型断言只能把一个类型转化为更特别或者更不特定的类型。
类型a转化为类型b,b应该是a的子集
const x = 'hello' as number // 报错,提示 string 和 number 没有重叠部分
可以先将 string as unknown
const x = 'hello' as unknown as number // ok
即这种格式:
const a = (expresssion as any) as T
字面量类型
字符串和数字字面量类型
除了一般的字符串和数字类型,我们还可以在类型位置指定特定的字符串和数字。
声明方式:
const constantString: 'Hello' // constantString 只能被赋予 'Hello'
这样的字面量类型没有什么价值,但是使用联合类型的方式组合多个字面量类型则更有用了:
const direction: 'left' | 'right' | 'top' | 'down'
这个例子中,direction
可以取上面四个字符串值
同样,数字字面量也可以,并且字面类型还可以和其他非字面量类型进行联合使用。
字面量推断
function handleRequest(url: string, method: 'GET' | 'POST') {
// ...
}
const req = { url: 'https://example.com', method: 'GET' }
handleRequest(req.url, req.method) // 报错
req.method
是 string
类型,而 handle
的method
参数是 'GET'|'POST'
字面量的联合类型。
-
可以使用类型断言来进行处理:
const req = { url: 'https://example.com', method: 'GET' as 'GET' } // 或 handleRequest(req.url, req.method as 'GET')
-
使用
const
来转成字面量类型const req = { url: 'https://example.com', method: 'GET' as 'GET' } as const
as const
作用是确保所有的对象属性都被转成字面量类型。
null 和 undefined
ts 中的 null 和 undefined 是否可以用,取决于
strictNullChecks
的配置:
off
:null 和 undefined 可以正常使用,null和undefined可以赋给任意类型。on
:会对空值进行检查
非空断言擦作符 !
在任意表达式后面添加 !
可以断言该值不会是 null
和 undefined
。
function liveDangerously(x? number | null) {
console.log(x!.toFixed())
}
枚举 enum
缩小类型范围
typeof
限定类型
true
值限定
-
&&
-
||
-
!
-
if
if
语句强制boolean
运算为false
值有:- 0
- NaN
- ''
- 0n (bigInt类型的0)
- null
- undefined
与之相反的值都是
boolean
true
相等限定
- ===
- !==
- ==
- !=
in
操作符
判断某个对象自身是否有某个属性字段。
instanceof
在 JS 中,x instanceof y
,会检查 x.prototype
中是否包含 y.prototype
来确定 x
是否是 y
的实例。