「1.9W字总结」一份通俗易懂的 TS 教程,入门 + 实战! - 掘金
也可以看一下这个网址的内容
声明基本数据类型
// 声明基本数据类型
let str:string = 'abc' //声明一个字符串类型的变量
let num:number = 123 //声明一个数字类型的变量
let bool:Boolean = Boolean(1) //或者直接赋值true
function fn():void{
// void 代表函数没有返回值
// 如果return 返回了值 则会提示错误信息
}
// 通过void 声明 undefined 和 null
let u:void = undefined
let n:void = null
// 通过 undefined 和 null 声明 这两个可以穿插使用
let u:undefined = undefined
let n:null = null
// 这个情况基本不会使用
let u:null = undefined
let n:undefined = null
ts的顶级类型 any 和 unknow
// any 和 unknown 类型都是 typeScript 中的顶级类型
let anys:any = 'abc'
let anys:any = 123
let anys:any = true | false
let anys:any = {}
let anys:any = []
let anys:any = Symbol()
// 使用 any 可以声明任意类型的数据
// unknow 和 any 用法上的一些区别
// unknow 类型
let unknow:unknow = { a:1 }
unknow.a //unknow是不可以使用的
// any类型
let unknow:any = { a:1 }
unknow.a //any是可以使用的
// unknow 类型只可以当做父类 不可以当做子类
let str:unknow = 'abc'
let str1:string = 'bbb'
str1 = str // 这种用法会报错 类型只可以当做父类 不可以当做子类
// any 类型 可以当做父类 也可以当做子类
let str:any = 'abc'
let str1:string = 'bbb'
str1 = str // 这种用法不会报错
// 相对比来说 unknow 比 any 更加的安全
// unknow 类型不可以调用属性和方法
// any 类型可以调用属性和方法
// unknow 只可以赋值 unknow 类型和 any 类型
接口 Interface 和对象使用 接口
// 定义接口
Interface A {
name:string, // 接口中定义一个字符类型的变量
fn():void // 接口中定义一个没有返回值的函数
}
// 声明一个 A接口类型的 obj 对象 对象中的类型必须和接口中的类型保持一致
class obj:A = {
name:'abc',
fn:()=>{} // 没有返回值的函数
}
// 接口重名
interface A {
name:string
}
interface A {
age:number
}
// 只读属性
interface Person {
age:number
name:string
readonly id:number
}
// interface 描述函数类型
interface ISum {
(x:number,y:number):number
}
const add:ISum = (num1, num2) => {
return num1 + num2
}
// 当两个接口重名时 会自动合并 对象中的类型就必须同时满足重名接口中的所有类型
class obj:A = {
name:'abc',
age:12
}
接口继承
Interface A {
name:string
}
// 接口 B 继承 接口 A
Interface B extends A {
age:number
}
// 接口继承通过 es6 中的 extends 实现接口继承
// 在对象声明使用时 对象中的属性类型 既得满足 B 中定义的 同时也得满足 A 中定义的
class obj:B = {
name:'abc',
number:123
}
可选式操作符 联合类型 接口任意类型[propName:string]
// 当定义接口时 不确定是否需要一个变量时 可以使用可选式操作服 ?来进行类型定义
Interface A {
name:string,
age?:number
}
// 当不确定是否需要 age 这个属性是 可以通过 ? 来声明该属性的类型 这样 age 属性就是可传可不传
class obj:A = {
name:‘abc’,
age:123 // 这个 age 属性就是可用可不用的 用和不用都不会报错
}
// 联合类型
Interface B {
name:string,
age:number,
// school 当一个属性 可能会有两个或多个类型时 我们可以考虑使用联合类型
// 联合类型用法
school:string | number //代用 这个 school 属性可以是字符串类型 也可以是数字类型
}
// 任意类型
Interface C {
name:string,
age:number,
// 当我们不知道在用的时候到底会有多少个属性时 可以用到任意类型 [propName:string] 关键字
[propName:string]:string //后面是 string 类型时 整个接口中所有的类型必须都是string 类型 不然会报错
[propName:string]:number //与上面 string 类型同理 接口中所有类型必须是 number 类型
[propName:string]:number | string //代用这个接口中可以是 数字类型 或者 字符类型
// [propName:string]:any //一般情况下 都是 any 类型
[propName:string]:any //代表接口中可以有任意类型的属性
}
只读属性 readonly
Interface A {
readonly name:string
}
class obj:A = {
name:'abc'
}
console.log(obj.name) // 只可以被读取
// error
obj.name = 'def' //被 readonly 定义的变量只可以读取 不可以修改
类
class Person {
name: string
constructor(name: string) {
this.name = name
}
speak() {
console.log(`${this.name} is speaking`)
}
}
const p1 = new Person('lin') // 新建实例
p1.name // 访问属性和方法
p1.speak()
// 使用 extends 关键字实现继承,定义一个 Student 类继承自 Person 类。
class Student extends Person {
study() {
console.log(`${this.name} needs study`)
}
}
const s1 = new Student('lin')
s1.study()
s1.speak() // 子类继承父类之后 也可以调用父类的方法
// super关键字
// 注意,上例中 Student 类没有定义自己的属性,可以不写 super ,
// 但是如果 Student 类有自己的属性,就要用到 super 关键字来把父类的属性继承过来。
// 比如,Student 类新增一个 grade(成绩) 属性,就要这么写:
class Student extends Person {
grade: number
constructor(name: string,grade:number) {
super(name)
this.grade = grade
}
}
const s1 = new Student('lin', 100)
// 类的一些属性 public 、 private 、 protected 、 static
// public,公有的,一个类里默认所有的方法和属性都是 public。
// 比如上文中定义的 Person 类,其实是这样的:
class Person {
public name: string
public constructor(name: string) {
this.name = name
}
public speak() {
console.log(`${this.name} is speaking`)
}
}
// public 可写可不写,不写默认也是 public
// private
// private,私有的,只属于这个类自己,它的实例和继承它的子类都访问不到。
// 将 Person 类的 name 属性改为 private。
class Person {
private name: string
public constructor(name: string) {
this.name = name
}
public speak() {
console.log(`${this.name} is speaking`)
}
}
// protected
// protected 受保护的,继承它的子类可以访问,实例不能访问。
// 将 Person 类的 name 属性改为 protected。
class Person {
protected name: string
public constructor(name: string) {
this.name = name
}
public speak() {
console.log(`${this.name} is speaking`)
}
}
// 实例访问 name 属性,会报错
let p1 = new Person('chen')
p1.name // 报错
// 子类可以访问
class Studeng extends Person {
study() {
console.log(`${this.name} needs study`)
}
}
// static
// static 是静态属性,可以理解为是类上的一些常量,实例不能访问。
// 比如一个 Circle 类,圆周率是 3.14,可以直接定义一个静态属性。
class Circle {
static pi = 3.14
public radius: number
public constructor(radius: number) {
this.radius = radius
}
public calcLength() {
return Circle.pi * this.radius * 2 // 计算周长,直接访问 Circle.pi
}
}
// 实例访问,会报错:
let c1 = new Circle(10)
抽象类 ( 这个暂时还没有更新完 还有一些 有时间在更新 )
// TS 通过 public、private、protected 三个修饰符来增强了 JS 中的类。
// 其实 TS 还对 JS 扩展了一个新概念——抽象类。
// 所谓抽象类,是指只能被继承,但不能被实例化的类,就这么简单。
// 抽象类有两个特点:
// 抽象类不允许被实例化
// 抽象类中的抽象方法必须被子类实现
// 抽象类用一个 abstract 关键字来定义,我们通过两个例子来感受一下抽象类的两个特点。
// 抽象类不允许被实例化
abstract class Animal {}
const a = new Animal()
// 定义一个抽象类 Animal,初始化一个 Animal 的实例,直接报错,
// 抽象类中的抽象方法必须被子类实现
abstract class Animal {
constructor(name:string) {
this.name = name
}
public name: string
public abstract sayHi():void
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
}
// 定义一个 Dog 类,继承自 Animal 类,但是却没有实现 Animal 类上的抽象方法 sayHi,报错,
// 正确的用法如下,
abstract class Animal {
constructor(name:string) {
this.name = name
}
public name: string
public abstract sayHi():void
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
public sayHi() {
console.log('wang')
}
}
数组类型
// 第一种定义数组的方法
let arr:number[] = [1,2,3] //定义一个数字类型的数组 这个数组中只可以有数字
let arr:string[] = ['啦啦啦','你是个麻瓜'] //定义一个字符类型的数组 这个数组中只可以有字符
let arr:any[] = [1,'aaa',true,[],{}] //定义一个任意类型的数组 这个数组中可以有任意类型
// 第二种定义数组的方法 数组泛型1
let arr:Array<number> = [1,2,3 //这也是声明的数字类型的数组 只可以有数字
let arr:Array<string> = ['啦啦啦','你是个麻瓜'] //字符型
let arr:Array<any> = [1,'aaa',true,[],{}] //任意类型
// 还有其他的类型也是在 <> 中写
// 多维数组声明
let arr:number[] = [] //一维数组
let arr1:number[][] = [ [] ] //二维数组
let arr2:number[][][] = [ [ [] ] ] //三维数组
// 泛型声明多维数组
let arr:Array<number> = [] //一维数组
let arr1:Array<Array<number>> = [ [] ] //二维数组
// 通过接口定义数组
Interface ArrNumber {
[index:number] : number // 这个是定义数字类型的数组
// [index:number] : string // 字符类型的数组
// [index:number] : any // 任意类型的数组
}
let arr:ArrNumber[] = [ 1,2,3 ] //接口声明的是数字类型的 数组中就只可以有数字
联合类型 类型断言 交叉类型
// 联合类型 |
let a:number = 123 // 正常定义类型是数字类型
// 当我们想要数字或者字符类型时 可以使用 联合类型
let b:string | number = 'aaa' // 这时 b 可以是字符类型 也可以是数字类型
// 定义了一个方法 想要返回一个布尔类型
// 此时 参数的类型可以时数字类型 也可以是布尔类型
function fn(type:number | boolean):boolean{
return !!type // 双感叹号 强转 1-> !1 转为 false false->!false 转为true
}
// 调用方法 传递实参时我们可以传递数字也可以传递布尔
fn(1)
fn(0)
fn(true)
fn(false)
// 类型断言 as 断言只能帮助我们欺骗编译器 但是改变不了最后的输出结果
// 简单理解 就是只会让编译器不会飘红报错 但是 最后的结果不会有其他的改变
function fn(num:number | string):void{
console.log(num.length) // 此时 数字是没有 length 属性的
console.log((num as string).length) // 我们可以使用断言 我们的参数就是字符串
}
fn('12345') //结果是 5
function fn(num:number | string):void{
console.log(num.length) // 此时 数字是没有 length 属性的
console.log((num as string).length) // 我们可以使用断言 我们的参数就是字符串
}
fn(12345) //当我们传参数是数字时 打印的值就是undefined 断言只能帮我们欺骗编译器 但是结果改变
interface A {
run:string
}
interface B {
build:string
}
let fn (type:A | B):void => {
console.log(type.run) //此时打印会报错 应为B接口上并没有run这个属性
// 使用断言
console.log((<A>type).run) //断言我们的type 是 A 类型的
// 也可以使用 as 方法
console.log((type as A).run) //断言我们的type 是 A 类型的
}
// 交叉类型 &
interface A {
name:string,
age:number
}
interface B {
sex:string
}
// 交叉类型在使用时 类似于 extends 继承 也是必须满足两个类型的条件
// 既得满足 A 中的条件 也得满足 B 中的条件
function fn(zihao:A & B):void{
console.log(zihao)
}
fn({
name:'子豪',
age:18,
sex:'男'
})
内置对象
// 正则
let regexp:RegExp = /\w\d\s/
// 日期
let date:Date = new Date()
let date:Date = new Date().getTime() //这么赋值的话会报错 getTime 是数字类型
// erroe
let error:Error = new Error('错误')
// 还有很多 懒得写了 百度查一下看看吧
class类
// 声明class类
class Person {
// 在这定义的必须在 constructor 中使用
name:string,
age:number,
sub:boolean
constructor( name:string,age:number,sub:boolean ){
// 在这里面不使用的话 可能会在上面未使用的变量名下飘红
this.name = name
this.age = age
this.sub = sub
}
}
new Person( '子豪',18,true )
// 类修饰符 public private protected static
public //内部外部都能访问到
private //私有变量只能在内部使用
protected //只能在内部和子类中使用
static //静态属性 也可以定义静态方法 static 定义的静态属性或方法 可以直接通过类名 Person 调用
// 在静态方法中不可以通过this来调用类中定义的变量 只可以访问 static 定义的变量
implements //用来约束类型
interface A {
run(type:boolean):boolean
}
interface B {
dev():void
}
class H {
name:string
constructor(name){
this.name = name
}
}
// newPerson 继承 H
// 通过 implements 定义类 的类型 有多个的话 , 隔开
class newPerson extends H implements A,B {
run(type:boolean):boolean{
return type
}
}
类型推论 和 类型别名
// 当我们没有给变量 声明类型时 我们赋值的类型就是变量的类型
// 编译器会帮我们 根据 赋值的类型对我们的变量做类型推断
let str = 'abc'
// 当我们已经赋值过字符类型时 变量会默认是个 string 类型
// 此时在赋值数字 会报错 提示 str 是一个 字符类型的
str = 345
// 类型别名
// 通过 type 定义 s 为 string 类型
type s : string
// s 使用 类型别名 定义为了 string 类型
let str:s = 'abc'
// 类型别名也支持联合类型
type s : string | number
// 可以赋值字符串 或者 数字
let str:s = 'abc'
let str:s = 123
// 当赋值不符合的类型时 会飘红报错
let str:s = true
// 函数式了类型别名
type fn()=>string
function fnc:fn()=> return 'abc'
// 值的类型别名
type T: 'on' | 'off'
// 使用值的类型别名之后 只可以赋值 定义好的值
let str:T = 'on'
let str:T = 'off'
never类型
// never 类型 表示1不应该存在的类型或状态
// 这么定义类型 str 类型会是 never类型的
// 编译器认为 即使 string 又是 number 类型 是一个不可能的事情 所以str 是 never 类型
let str:string & number
// 函数 当函数内保抛出异常时 那函数就永远不可能有返回值 所以返回值是 never 类型
function fn(message:string):never{
throw new Error(message)
}
function loop():never{
// 函数内部是一个死循环的话 也是永远不会有返回值
while(true){
}
}
// 接口
interface A {
type:'子豪'
}
interface B {
type:'20'
}
// 联合类型
let All = A | B
function fn(val:All){
switch(val.type){
case: '子豪';
break;
case: '20';
break;
// default也称之为兜底逻辑 当case条件全部不满足时 会走兜里逻辑
default:
const check:never = val;
break;
}
}
迭代器 [Symbol.iterator]()
let arr:Array<number> = [ 1,2,3 ]
let it:Iterator<number> = arr.[Symbol.interator]()
it.next() // 执行结果是一个对象 { value:数组中的值 , done:false }
// { value:数组中的值 , done:false }
// 对象中的 done 为true 时 代表数组执行完了 没有值了
// 迭代器测试
let arr:Aarray<number> = [ 1,2,3,4 ]
let set:Set<number> = new Set([ 1,2,3,4 ])
// type mapKeys:string | number
// Map()有两个参数 一个 key 一个value
let map:Map<mapKeys,mapKeys> = new Map()
map.set('1','王爷')
map.set('2','皇上')
function gen(erg:any){
let it:Iterator<any> = erg[Symbol.iterator]()
let next = { dnoe:false }
while(!next.done){
next = it.next()
if(!next.donw){
console.log(next)
}
}
}
gen(arr)
// 结果
// { value:1 , done:false }
// { value:2 , done:false }
// { value:3 , done:false }
// { value:4 , done:false }
gen(set)
// 结果
// { value:1 , done:false }
// { value:2 , done:false }
// { value:3 , done:false }
// { value:4 , done:false }
gen(map)
// 结果
// { value:['1',‘王爷’] , done:false }
// { value:['2',‘皇上’] , done:false }
函数泛型
// 函数泛型
正常写一个函数 进行类型规范
这种写法会比较麻烦
function fn(num:number):number{
return num
}
fn(123)
这样我们可以使用泛型进行约束
function fn<T>(str:T):T{
return str
}
// 在调用函数时规定类型 那么 函数接受的参数就都是字符型
fn<string>('123')
// number就是数字类型
fn<number>(123)
// 也可以使用接口定义规范
// 定义一个接口
interface len {
length:number
}
// 使用联合类型 将我们想到规定的类型 继承接口定义的类型
// 通过定义的 len 接口来约束 T 这个类型
function fn<T | len> (arg:T) {
return arg.length
}
// 只能传一些带有 length 的值
fn([1,2,3,4])
fn('12346')
使用keyof约束对象
// 这个函数参数接受一个对象 还有一个对象的key
function prop<T>(obj:T,key){
// 返回是这个key在对象中的值
return obj[key]
}
// 对象可能是动态的
let o = { a:1,b:2,c:3 }
prop(o,'a') //在这里调用函数的返回值 就是 对象中 a 的值
//当我们传一个不存在的key时 ts 不会提示警告 这时 我们就需要用到 keyof 来拿到对象中的每一项
prop(o,'d')
// 使用 keyof 来获取
// <T,K extends keyof T> 这里的 K 使用 keyof 继承 T 这个类型接受的值
// K 的值是一个联合类型 a | b | c 这样 在传一个不存在的值时 就会报错
function prop<T,K extends keyof T>(obj:T,key:K){
// 返回是这个key在对象中的值
return obj[key]
}
// 对象可能是动态的
let o = { a:1,b:2,c:3 }
prop(o,'a') //在这里调用函数的返回值 就是 对象中 a 的值
//当我们传一个不存在的key时 ts 不会提示警告 这时 我们就需要用到 keyof 来拿到对象中的每一项
prop(o,'d')
泛型类
// 声明一个 class 类 使用泛型进行约束
class Sub<T>{
attr:<T>[] = []
add(a:T):<T[]>{
return [a]
}
}
// new Sub<number> 这个 number 代表了 约束类的 T 是number 类型
// 那么在类中 所有使用 T 进行约束的值 都必须是number 类型
let s = new Sub<number>()
// 使用 T 约束的 attr 就是一个number类型的数组
s.attr = [1,2,3] // 在使用时 就只能传数字类型的数组
s.add(123) // 这个函数也就是能接受 number 类型的值
可选链操作符 和 双问号表达式
// 可选链操作符 ?.
// 用法
let arr = {}
// 当我们声明了一个对象时 想读取里面的children属性
arr.children // 此时对象中是没有这个属性的 读取不到的时候会报undefined
// 当我们接着读取 children 的 length 长度
arr.children.length // 这时会报错 因为undefined属性是没有length属性的
// 这时我们可以用可选链操作符 ?.
arr.children?.length // 这样 就不会报错了 而是返回undefined
// 双问号表达式 ??
// 在使用可选链操作符时 一般配饰 ?? 来使用
// 一般是判断左边的值 如果左边值为 null 或者 undefined 时 就会返回右边的值
// 也就是 左边为 null 或者 undefined 时就会返回右边的 {}
// 注意 0 和 false 不做处理 不是反对左边为false时就会返回右边的值
// 而是正常返回0 或 false
arr.children.length ?? {}