TypeScript基础学习
# 起航
TypeScript是JavaScript的超集,本来呢,我对它并不感兴趣,但是现在大势所趋,我还是必须得学了额。
# 类型
TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便使用。
let str = "1"; //根据初始的赋值来推导出变量的类型。以后str的类型不能改了。
//str = 2; // 报错,原因:变量在定义的时候,类型已经确定下来了,不能修改
const num = 1; // 常量不能改版指向(不能被修改)所以它的值就是它的类型
//num = "2" // 报错:原因:常量不能改变指向(不能被修改)
//const num1 = true;
2
3
4
5
// ts原始类型有哪些 ?
// js基础数据类型:
// number, string, booleanm, undefined, null, symbol
// ts原始类型就是js基础数据类型
let str1:string = "1";
let bool:boolean = false;
let num1:number = 10;
num1.toFixed(2);
//str1.toFixed(2) 报错
let und:undefined = undefined;
let nul:null = null;
let sy:symbol = Symbol("123");
let vo:void = undefined;
function a():void{}
// function a():undefined{} 报错
// 在ts中函数没有返回值,函数类型就是void
function b():undefined{return undefined}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 布尔值 boolean
let isDone:boolean = false
# 数字 number
和JavaScript一样,ts里所有的数字都是浮点数,这些浮点数的类型是number
,支持十进制,八进制,二进制,十六进制,还支持Infinity
和NaN
let decLiteral:number = 6
let hexLiteral:number = 0xf00d
let binaryLiteral:number = 0b1010
let octalLiteral:number = 0o774
let infinityNumber:number = Infinity
let nanNumber:number = NaN
2
3
4
5
6
# 字符串 string
let name:string = "bob"
name = "smith"
2
还可以使用模板字符串,它可以定义多行文本和内嵌表达式。
let myname:string = `Gene`
let age:number = 37
let sentence:string = `Hello, my name is ${myname}.
I'll be ${age + 1} years old next month.`
// Hello, my name is Gene.
//I'll be 38 years old next month.
2
3
4
5
6
# any 和 unknown
any:任意类型, unknown:不知道的类型
ts中类型有六个层级,上级的类型可以包含下级的所有类型:
- top type 顶级类型: any, unknown
- Object
- Number String Boolean
- number string boolean
- 普通的数字,普通字符串,true/false
- never
let a:unknown = 1
let b:any = '123'
let c:number = 12
let d:unknown = {'name':'daodao'}
a = b
b = a
a = c
c = a // 报错,unknown不能给number类型赋值
console.log(d.name) //报错,unknown不可以读任何属性
2
3
4
5
6
7
8
9
10
需要注意的是,unknown只能赋值给自身或者是any,不能给别的类型赋值,而且无法读任何属性,方法也不可以调用。
// 不推荐使用any, any绕过类型校验
let a:any = 1;
a = '10';
a = [];
a = {b:10};
a.toFixed(2); // 绕过了不检测
let n:unknown;
n = 1;
n = "10";
n = [1,2];
//n.toFixed(2); // 有做类型校验,除非上面写number,才不会报错
if(typeof n === 'number'){
n.toFixed(2)
}else if(typeof n === 'string') {
n.concat()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# never类型
使用never类型来表示不应该存在的状态
// 返回never的函数必须存在无法达到的终点
// 因为必定抛出异常,所以error将不会有返回值
function error(message:string):never {
throw new Error(message)
}
// 因为存在死循环,所以Loop将不会有返回值
function loop():never {
while(true) {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# never与void的差异
// void类型只是没有返回值 但本身不会出错
function Void():void {
console.log()
}
// 只会抛出异常但没有返回值
function Never():never {
throw new Erroe('aaa')
}
// never在联合类型中会被直接移除
type A = void | number | never
2
3
4
5
6
7
8
9
10
11
12
# never类型的一个应用场景
type A = '唱'|'跳'|'rap'
function Ikun(value:A) {
switch(value) {
case '唱':
break
case '跳':
break
case 'rap':
break
default:
// 是用于场景兜底逻辑
const error:never = value
return error
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
但如果新来一个同事新增了一个篮球,我们必须手动找到所有switch代码并处理,否则将有可能写出bug,这个bug很难被发现,但是TS可以发现这个问题
type A = '唱'|'跳'|'rap'|'篮球'
function Ikun(value:A) {
switch(value) {
case '唱':
break
case '跳':
break
case 'rap':
break
default:
// 是用于场景兜底逻辑
const error:never = value
return error
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
由于任何类型都不能赋值给never类型的变量,所有当存在进入default分支的时候,ts的类型检查会帮我们发现这个问题。
# Object object 以及 {}
Object: 原型链的顶端其实就是Object,也就是意味着所有的原始类型以及对象类型最终都指向Objet,因此,在ts中Object就表示包含了所有的类型,它可以等于任何一个值。
let a:Object = 1 a = {'name':'abc'} a = 'str'
1
2
3object 常用语泛型约束,表示非原始类型,也就是除
number
,string
,boolean
,symbol
,null
或undefined
之外的类型let a:object = {'name':'abc'} a = 1 //报错 a = 'str' //报错
1
2
3{} 和Object一样,可以为任何类型,但是不允许修改里面没有的属性值
let c:{} = {'a':123} c.a = 456 //报错 c.b = 'dfa' //报错
1
2
3
// object(常用) Object {}
let obj:object = {a:1};
let arr:object = [1];
//let num:object = 20 //报错,不能将类型”number"分配给类型"object"
//let str:object = "hello" //报错
// object不包含基础数据类型
let obj1:Object = {b:1};
let arr1:Object = [2,3];
let num1:Object = 2;
let str1:Object = "2";
let bool1:Object = true;
// Object 包含基础数据类型
let obj2:{} = {b:1};
let arr2:{} = [2,3];
let num2:{} = 2;
let str2:{} = "2";
let bool2:{} = true;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 数组
- 定义普通数组类型
let arr:number[] = [1,2,3,4,5]
let arr1:boolean[] = [true,false]
let arr2:Array<number> = [1,2,3]
2
3
- 定义对象数组
interface X {
name:string
}
let arr3:X[] = [{name:'daodao'},{name:'mimi'}]
2
3
4
5
- 定义二维数组
let arr4:number[][] = [[1],[2],[3]]
let arr5:Array<Array<number>> = [[1],[2],[3]]
2
- 大杂烩数组
let arr6:any[] = [1,'str',true, {}]
// 也可以用元祖
let arr7:[number,string,boolean,{}] = [1,'str',true, {}]
2
3
- 定义函数的剩余参数类型
function a(...args:number[]) {
console.log(args)
// arguments是一个类数组,定义它可以用ts内置类型 IArguments
let a:IArguments = arguments
}
a(1,2,3) // [1,2,3]
2
3
4
5
6
7
# 元组类型
如果需要一个固定大小的不同类型值的集合,我们需要使用元素,元组就是数组的变种,元组是固定数量的不同类型的元素的集合。
let arr:[number,string] = [1,'string']
let arr2: readonly [number,boolean,string,undefined] = [1,true,'sring',undefined]
2
3
元组类型还可以支持自定义名称和变为可选的
let a:[x:number,y?:boolean] = [1]
# 联合类型
// 这里的phone既可以是number也可以是string
let phone:number | string = 12312
phone = 'abc'
// type既可以传number也可以传boolean
let fn = function(type:number|boolean):boolean {
return !!type
}
2
3
4
5
6
7
8
// | 联合类型 或
let numOrStr: number | string = 10;
numOrStr = "str"
// 1|'2' 在这里的1和'2'是类型, 常量, 表示numAndStr2 的值只能是1 或者 '2'
let numOrStr2: 1|'2' = 1
numOrStr2 = "2"
// numAndStr2 = 2 // 报错
let obj:{a:1}|{b:'3'}; // | 或,表示要么有a属性,要么有b属性,都有也可以, 不能有其他属性
obj = {a:1};
obj = {a:1,b:'3'}
obj = {b:'3'}
//obj = {a:1,c:2} // 报错
2
3
4
5
6
7
8
9
10
11
12
13
14
# 交叉类型
interface People {
name:string;
age:number;
}
interface Man{
sex:number
}
// man必须同时满足People和Man
const daodao = (man:People & Man):void => {}
daodao({
name:'daodao',
age:18,
sex:1
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// & 交叉类型
let a:number&string; // 不会有任何值满足这个类型,一般不会这么写
// & 都必须有 name,age, height属性,都得有
// 如果一个属性出现多次类型的设置,需要都满足
let obj:{name:string,age:number} & {height:number, age:18}
obj = {name:"zhangsan",age:18,height:1.80}
// & 换成 | 才可以少属性
2
3
4
5
6
7
8
9
10
# 联合交叉类型
// | &
// &&优先于||
// console.log(1||2&&3); //1
// & 优先于 |
let obj:{name:string} & {age:number} | {name:number} & {age:string}
obj = {
name:123,
age:"123"
}
obj = {
name:"123",
age:123
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 类型断言
类型断言不可以乱用哦!
let fn1 = function(num:number|string):void {
// 类型断言只能欺骗ts编译器,不能避免运行时的错误
console.log((num as string).length)
}
fn1(12345)
interface A {
run:string;
}
interface B {
build:string;
}
let fn2 = (type: A|B):void => {
// 类型断言只能欺骗ts编译器,不能避免运行时的错误
// !类型断言不能乱用!!
console.log((type as A).run)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 类型别名
// 自定义一个类型
type StrOrNum = string | number
let str:StrOrNum = "1";
str = 1;
type ObjType = {a:number&2, b:string}
// type ObjType = {c:string}
let obj:ObjType = {
a:2,
b:"123"
}
// interface 和 type 的区别
// 都可以用来自定义类型
// 类型别名支持联合和交叉定义
// 类型别名不支持重复定义,接口可以
interface AItf{
a:string
}
// 用类型别名保存接口上的某个类型
type Atype = AItf['a']
let str2:Atype = "10"
type color = 'red' | 'blue' | 'green' | string & {}
let c:color = 'red'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 接口
接口就是对值的结构进行类型检查,接口和值不能多属性也不能少属性。
interface Example {
name: string;
age:number;
}
let a:Example = {
name:'daodao',
age:18
}
2
3
4
5
6
7
8
9
# 接口重名
重名的两个接口会合并成为一个接口
interface A {
name:string;
age:number;
}
interface A {
money:number
}
let daodao:A = {
name:'daodao',
age:18,
money:1000000000000
}
2
3
4
5
6
7
8
9
10
11
12
# 任意key
有时候实在不能把每个属性都列举出来,比如后端返回了其他的属性
interface B {
name:string;
age:number;
[propName:string]:any;
}
let b:B = {
name:'b',
age:100,
a:1,
b:2,
c:3
}
2
3
4
5
6
7
8
9
10
11
12
# 只读和可缺省属性
有的属性比如id,并不希望它能被外部修改,可以加上readonly变为只读属性,而有的属性可有可无,可以加?代表可缺省
interface C{
readonly id:number;
name:string;
age?:number;
readonly cb:() => boolean;
}
let c:C = {
id:1,
name:'c',
cb:() => {
return false
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 接口继承
接口继承和接口重名类似
interface D {
name:string
}
interface E extends D {
age:number
}
let e:E = {
name:'e',
age:20
}
interface NameItf{
readonly name:string // readonly 属性名,表示这个属性只允许读取,修改就报错
}
interface AgeItf{
age?:number //属性名?表示这个属性可以缺省。(定义数据的时候不写也没问题)
}
// 接口继承的格式,特点是具有父接口的属性类型
interface PersonItf extends NameItf,AgeItf {
height:number
}
let p:PersonItf;
p = {
name:"张三",
height:1.80
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 定义数组
interface ArrItf{
//[idx:number]下标类型:值类型
[idx:number]:number|string
}
2
3
4
# 定义函数
定义函数类型就想一个只有参数列表和返回值类型的函数定义
interface Fn {
(name:string):number[]
}
const fn:Fn = function(name:string) {
return [1]
}
interface FnItf{
// 形参及类型:返回值类型
(p:string,a:number):void
}
let fn:FnItf = (p:string,a:number) => {
}
fn("",1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 默认参数 参数名:number=3 这个参数的默认值是3
function fn(a:number,b:number=3):number {
return a+b
}
fn(1,2)
fn(5)
// 缺省参数 参数名?表示可以被缺省的参数
function fn1(a:number,b?:number) {
return 1
}
fn1(1,2)
fn1(1)
// 剩余参数
function fn2(a:number,b:number,...arr:number[]){
console.log(a,b)
console.log(arr)
}
fn2(1,2,3,4,5)
let arr1 = [1,2,3]
let arr2 = [...arr1]
arr1[0] = 4
console.log(arr1) // [4,2,3]
console.log(arr2) // [1,2,3]
let obj1 = {a:1,b:2,c:[1,2,3]}
let obj2 = {...obj1} //浅拷贝
obj1.a = 100
obj1.c[0] = 1000
console.log(obj1) // {a:100,b:2,c:[1000,2,3]}
console.log(obj2) // {a:1, b:2, c:[1000,2,3]}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 内置对象
# ECMAScript的内置对象
const regexp:RegExp = /\w\d\s/
const date:Date = new Date()
const error:Error = new Error('错误')
const b:Boolean = new Boolean(1)
const n:Number = new Number(true)
const s:String = new String('123')
2
3
4
5
6
7
8
9
10
11
# DOM和BOM的内置对象
其实这个不用记,ts会自动推断,鼠标移上去就可以看了
const list:NodeListOf<HTMLLIElement> = document.querySelectorAll('li')
const body:HTMLElement = document.body
const div:HTMLDivElement | null = document.querySelector('div')
document.body.addEventListener('click',(e: MouseEvent) => {
})
2
3
4
5
6
7
8
9
# Promise
function promise():Promise<number> {
return new Promise<number>((resolve,reject) => {
resolve(123)
})
}
interface DataItf{
a:number,
b:number
}
interface ResItf {
code:number;
data:DataItf[] //{a:number,b:number}[],
message:string
}
// promise对象: p对象名:Promise<res的类型>
let p:Promise<ResItf> = new Promise((resolve, reject) => {
resolve({
code:0,
data:[{a:1,b:2},{a:11,b:22}],
message:""
})
})
p.then(res => {
if(res.code === 0) {
res.data.map(item => item.a)
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Class
// 定义类的同时,会创建一个相同名字的接口
class Person {
// 定义属性前,应该先声明这个属性的类型,也可以同时设置默认值
myName:string = "默认名称"
constructor(n:string) {
this.myName = n
}
getName() {
return this.myName
}
}
let p = new Person("zhangsan")
console.log(p.myName) // zhangsan
console.log(p.getName()) //zhangsan
// 以上这个类,相当于下面这个接口
// interface Person {
// myName:string,
// getName:() => string
// }
let obj:Person = {
myName:"",
getName() {
return ""
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 类的继承
class Person {
myName:string
constructor(n:string) {
this.myName = n
}
getName() {
return this.myName
}
}
class Male extends Person {
age:number
constructor(n:string,age:number) {
super(n) // 调用回父类的constructor,并把参数传进去
this.age = age
}
getName() { // 重写
return "我叫"+this.myName
}
getAge() {
return this.age
}
}
let m = new Male("zhangsan",17)
console.log(m.myName) //zhangsan
console.log(m.getName()) //我叫zhangsan
console.log(m.getAge()) // 17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 类的修饰符
// 类里面,定义的属性,默认的修饰符就是public,public修饰的属性和方法都可以在类的内部,类的外部可以访问,子类也可以访问。
// protected 受保护的,类里面,子类里面都可以访问,类的外面不能访问
// private 私有的,在本类里面可以访问,子类和类的外面不能访问
// readonly 设置属性只读,不能被修改
class Person{
public readonly myName:string
// protected myName:string
// private myName:string
static title:string = "title的值" // 静态属性/成员,是给类去用的
constructor(n:string) {
this.myName = n
}
public getName() {
return this.myName
}
}
console.log(Person.title)
Person.title = "修改后的title的值"
console.log(Person.title) // 修改后的title的值
let p = new Person("李四")
//console.log(p.title) 报错!
class Male extends Person{
age:number
constructor(name:string, age:number) {
super(name)
this.age = age
}
getName() {
return "我叫"+this.myName
}
}
let m = new Male("张三",17)
console.log(m.myName)
console.log(m.age)
console.log(m.getName())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 抽象类
用abstract定义的类是抽象类,抽象类不可以被实例化
abstract class A {
name:string
constructor(name:string) {
this.name = name
}
print(): string {
return this.name
}
abstract getName():string
}
// B类实现了A定义的抽象方法,如果不实现就会报错,我们定义的抽象方法必须在派生类实现
class B extends A {
constructor() {
super('小满')
}
getName(): string {
return this.name
}
}
let b = new B();
console.log(b.getName());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 工具类型 Reqiured 和 Partial
interface PItf {
name:string;
age:number;
height?:number;
}
// interface PItf2 {
// name?:string;
// age?:number;
// }
//type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
/*
keyof T name|age
{
name?: string|undefined;
age?: number|undefined;
}
for(key in 对象)
// Partial 部分的
*/
// 作用:把<>里面这个接口类型的属性设置为可缺省的属性
let obj:Partial<PItf> = {
name:"123"
}
type Required<T> = { [P in keyof T]-?: T[P]; }
/**
* keyof T name|age|height
* -? 抵消,去掉这个问号的效果
* {
* name:string,
* age:number,
* height:number
* }
*/
// 作用:把<>里的这个接口类型的属性设置为不可缺省的属性
let obj2:Required<PItf> = {
name:"",
age:12,
height:1.80
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 枚举
// 枚举不是用来定义类型,列举数据用的
// enum Xxx{
// a = 10,
// b = "200"
// }
enum StatusCode{
success=200,
paramsError=400,
serverError=500
}
let code:string|number= 200;
if(code===StatusCode.success){
console.log("成功")
}else if(code===StatusCode.paramsError){
console.log("失败")
}else if(code===StatusCode.serverError) {
console.log("失败,服务器问题")
}
enum StatusCode2{
success,
paramsError=400,
serverError
}
console.log(StatusCode2.success, StatusCode.paramsError, StatusCode.serverError)
// 0, 400, 401
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28