TypeScript:
-
typescript的定位是静态类型语言,在写代码的阶段就能检查错误,而非运行阶段
-
类型系统是最好的学习文档,增加了代码的可维护性
-
需要理解接口(interface),泛型(Generics),类(class)等
-
ts最后会被编译成js
一、使用
安装: npm install typescript -g
新建.ts文件,编译命令: tsc 文件名
优化编译
- 解决ts和js冲突问题: tsc --init //生成配置文件
- 自动编译:tsc --watch
- 发出错误: tsc -nnoEmitOnError 文件名
显示类型
function great(person:String,date:String){ console.log(`hello,${person},today id ${date}--`) } great('libai','2021/4/3')
降级编译(默认是以ES2016编译的)
tsconfig.json: target: 'es2016' //编译后的代码可以支持es2016的语法
严格模式
tsconfig.json:
“strict”: true "noImplicitAny": true, "strictNullChecks": true
二、基元类型string,number,boolean,数组,any
let arr:number[] = [1,2,3,5] let arr2 : Array<number> =[1,2,34] //泛型 arr2 =[1,2,34] let str:string = 'hello' let num:number = 100 let bool :boolean = true
any: 不希望某个特定值导致类型检测错误,即表示对所有类型都不做校验, 以下代码不会报错
let obj:any={
x:0
}
obj.foo()
obj()
obj.bar = 100
obj = 'hello'
变量上的类型注释
//变量的类型注释
let myName: string = 'test'
let test = 'test'
//函数的类型注释
function great(name: string){
console.log(`${name}`)
}
great('test')
//函数的返回值类型注释出现在函数的参数列表之后
function getNumber() : number{
return Math.random()
}
//匿名函数
//即使我们没有显示定义函数参数类型,ts也会根据上下文推断该参数的类型
//统称为上下文类型
const names = ['test','rts']
//知道函数执行的地方
names.forEach(function (s){
})
names.forEach((item)=>{
})
对象类型
function printCot(temp: {x: number, y: number}){
console.log(`${temp.x},${temp.y}`)
}
printCot({
x:1,
y:2
})
//last?: string表示last是可选的,不是必须传的
//不能传入除已定义的属性之外的属性,否则会报错
function getName(obj:{first: string,last?: string}){
//console.log(obj.last.toUpperCase()
if(obj.last !== 'undefined'){
console.log(obj.last)
}
return `${obj.first}${obj.last}`
}
getName({first:'test'})
联合类型
联合类型表示两个或者多个其他类型组成的类型,表示可能是这些类型中的任何一种的值: union类型
联合类型可以使得变量类型范围变宽,但是使用的时候要注意缩小类型范围
function printId(id: string|number|Array<number>){
// console.log(id?.toUpperCase())
if(typeof id === "string"){
console.log(id?.toUpperCase())
}else if(typeof id === 'number'){
console.log(id?.toFixed())
}else {
console.log(id)
}
}
printId('101')
printId(101)
function getFirstName(x:string | Array<number>){
console.log(x.slice(0,3))
}
getFirstName('12345566')
getFirstName([1,2,3,45])
类型别名
类型提前声明: type 变量类型
type Point = {
x: number,
y: number
}
function printDot(pt: Point){
}
printDot({
x:100,
y:100
})
type ID = number | string
function getId(id: ID): string{
return id?.toString()
}
接口: interface
类型别名和接口的区别:几乎所有的接口都可以使用type + 类型别名来定义,区别如下:
接口的扩展使用extends,类型别名的扩展使用&
向接口中添加新字段可以重复定义接口,而类型别名中重复定义类型会报错
interface PointTest {
x: number,
y:number
}
function printDotId(pt: PointTest){
}
printDotId({
x:100,
y:100
})
//类型别名和接口的区别:
//几乎所有的接口都可以使用type + 类型别名来定义,区别如下:
//扩展接口
interface Animal{
name: string
}
interface Bear extends Animal{
honey:boolean
}
const bear: Bear = {
name:'libai',
honey: true
}
console.log(bear.name)
console.log(bear.honey)
//type的交叉扩展
type AnimalTest = {
name: string
}
type BearTest = Animal & {
honey: Boolean
}
const bearTest: Bear = {
name:'hello',
honey:false
}
console.log(bearTest.name)
console.log(bearTest.honey)
//如何向现有的接口或者类型里添加字段
//接口类型可以重新定义重名的接口去添加新的字段
interface MyWindow{
count: number
}
interface MyWindow{
title: string
}
const temp: MyWindow = {
title:'hello',
count:100
}
//类型创建以后是不能更改的,否则会报错
type MyWindowTest = {
title: string
}
type MyWindowTest = {
count: number
}
类型断言
断言: 当不知道某个变量类型时,断言为某个已知的类型
const myCanvas = document.getElementById('myCanvas') as HTMLCanvasElement
const myCanvas2 = <HTMLCanvasElement>document.getElementById('myCanvas')
const x = ('hello' as any) as number
const y = ('hello' as unknown) as number
文字类型
let testString = 'test'
const constantString = 'test'
let ut: 'hello' = 'hello'
//alignment: 'left'|'right'|'center':表示alignment的值只能时这其中的一个
function printText(s:string,alignment: 'left'|'right'|'center'){
}
printText('test','left')
//: -1 | 0 | 1表示返回值时-1,0,1其中之一
function compare(a:string,b:string): -1 | 0 | 1{
return a ===b ? 0: a>b ? 1 : -1
}
interface Options{
width:number
}
//x: Options|'auto':表示x的值要么时Options类型的,要么是'auto'
function configure(x: Options|'auto'){
}
configure({width:12})
configure('auto')
//变量类型推断
function handleRequest(url: string,method:'GET'|'POST'|'GUESS'){
}
handleRequest(req.url,req.method)//req.method会被认为是一个除get/post/guess之外的特殊字符串
const req = {
url:'https://test',
method:'GET' as 'GET'
}
handleRequest(req.url,req.method)//req.method
//或者
handleRequest(req.url,req.method as 'GET')
//或者
const reqtest = {
url:'https://test',
method:'GET' as 'GET'
} as const
null和undefined类型
null指的是不存在,undefined表示未初始化的值
let h: undefined = undefined
//类型推断: 因此undefined可以省略
let z:null = null
function doSomething(x:string | null){
if(x === null){
console.log(x)
}else{
console.log(x.toUpperCase())
}
}
function live(x?:number | null){
console.log(x?.toFixed(2))
console.log(x!.toFixed(2)) //!表示当前值不可能是null或者undefined
}
枚举
通过enum关键字来定义
enum Description{
left = 1,
right,
up,
down
}
//设置left的值后其余值会自动递增
console.log(Description.left)
console.log(Description.right)
bigint,symbol
bigint: 非常大的整数
symbol: 全局唯一使用的
const tempTest: bigint = BigInt(100)
const anotherInt: bigint = 100n
const firstName = Symbol('name')
const secondName = Symbol('name')
类型
类型缩小:将类型转化为比声明更具体的类型
从宽类型到窄类型的变化,经常用于处理联合类型
typeof 类型守卫
typeof null和typeof Array的值都会是Object
//ts代码
function printAll(strs: string|Array<string> | null){
if(typeof strs === 'string'){
console.log(strs.toUpperCase())
}else if(typeof strs === 'object'){
strs?.forEach(item => console.log(item))
}
}
printAll('hello')
printAll('hello'.split(''))
//编译后的js代码
"use strict";
function printAll(strs) {
if (typeof strs === 'string') {
console.log(strs.toUpperCase());
}
else if (typeof strs === 'object') {
strs === null || strs === void 0 ? void 0 : strs.forEach(item => console.log(item));
}
}
printAll('hello');
printAll('hello'.split(''));
真值检查: && || !!
等值检查: === ,!==,< , >
function example(x: string|number, y: number | boolean){
if(x === y){
x.toString()
y.toString()
}
}
function tets(strs: string | Array<string> | null ){
if(strs !== null){
if(typeof strs === 'string'){
console.log(strs.toUpperCase())
}else if(typeof strs === 'object'){
strs.forEach(item => console.log(item))
}
}
}
//null == undefined //true
interface Container{
value: number | null | undefined
}
function test(x:Container,factor: number){
if(x.value != null){
console.log(x.value)
}
}
test({value:5},6) // 5
test({value:undefined},6) //
in操作符缩小:确定对象是否有某个属性
type Fish ={
swin: ()=> void,
}
type Bird ={
fly: ()=> void,
}
type Human = {
swin?: ()=>void,
fly?: ()=>void
}
function move(animal: Fish| Bird | Human){
if('swin' in animal){
return (animal as Fish).swin() //断言为Fish
}
return (animal as Bird).fly() //断言为Bird
}
instanceof操作符缩小:检查对象是否是某个类的实例
function logValue(x: Date | string){
if(x instanceof Date){
console.log(x.toUTCString())
}else{
console.log(x.toUpperCase())
}
}
logValue(new Date()) //Wed, 12 Jul 2023 03:11:07 GMT
分配缩小:利用三元运算符进行类型分配
// let x: number | string
let x = Math.random() > 0.5 ? 10 : 'hello'
x = 1
x = 'good'
使用类型谓词
类型谓词: pet is FishTest : 属性 is 类型
type FishTest={
name:string,
swim: ()=>{}
}
type BordTest = {
name:string
fly:()=>{}
}
// 如果pet有swim方法,那pet就是一个FishTest类型,
function isFish(pet: FishTest | BordTest): pet is FishTest{
return (pet as FishTest)?.swim !== undefined
}
function getSmallPet(): FishTest | BordTest{
let fish: FishTest = {
name:'sharkey',
swim: ()=>{return 0}
}
let bird: BordTest = {
name:'sparrow',
fly: ()=>{return 0}
}
return true ?bird : fish
}
let pet =getSmallPet()
console.log(isFish(pet))
if(isFish(pet)){
pet.swim()
}else{
pet.fly()
}
const zoo: Array<FishTest | BordTest> = [getSmallPet(),getSmallPet(),getSmallPet()]
const underWater:FishTest[] = zoo.filter(isFish)
const underWater2:FishTest[] = zoo.filter(isFish) as FishTest[]
console.log(underWater)
console.log(underWater2)
受歧视的unions:
问题:
interface Shape{
kind:'circle' | 'square',
radius?: number,
sideLength?:number
}
function getArea(shape: Shape){
return Math.PI * shape.radius ** 2 //无法判断shape是否是圆形
}
解决方案: 将shape拆分为两个类型,使得radius和sideLength都成为必须有的属性
interface Cicle{
kind:'circle',
radius: number
}
interface Square{
kind:'square',
sideLength: number
}
type Shape = Cicle | Square
function getArea(shape: Shape){
if(shape.kind === 'circle'){
return Math.PI * shape.radius ** 2
}
return shape.sideLength ** 2
}
never类型与穷尽性检查
never: 不应该存在的状态
函数
函数类型表达式:
fn: (a:string) => void
type greatFunction = (a:string)=> void
function great(fn: greatFunction){
fn('hello')
}
function printToConsol(str:string){
console.log(str)
}
great(printToConsol)
调用签名
给函数定义签名:
type Description = {
description: string, //定义属性
(arg: number):boolean //参数是number类型,返回值是bool
}
function doSomething(fn: Description){
console.log(fn.description + 'returned' + fn(6))
}
function fn1(n: number){
return true
}
fn1.description = 'hello'
doSomething(fn1) //
构造签名
//js是没有函数签名的,因为其参数的可变性
class Ctor{
s:string
f:string
constructor(s:string,f:string) {
this.s = s
this.f = f
}
}
type SomeConstructor = {
new (s:string,f:string):Ctor,
}
function fn2(ctor:SomeConstructor){
return new ctor('hello', 'world')
}
const temp = fn2(Ctor)
console.log(temp.s)
//在同一个类型中结合调用和构造签名
interface CallOrConstructor{
(n?: number):number
new (s:string): Date
}
function fn(date: CallOrConstructor){
let d = new date('2021-2-12')
let e = date(100)
}
泛型函数
泛型: 两个值之间存在着对应关系,使用泛型去解决
作用: 使得输出的值和输入的值类型相同
//泛型
function firstElement(arr :any[]){
return arr[0] //返回值的类型是由return 决定的
}
firstElement([1,22,3])
console.log(typeof firstElement([1,22,3])) //number
function secondElement<Type>(arr: Array<Type>):Type | undefined{
return 100 //会报错,输入的是泛型,输出的也必须是泛型
}
secondElement([1,22,3])
function secondElement<Type>(arr: Array<Type>):Type | undefined{
return arr[0]
}
secondElement([1,22,3]) //Type是number
//或者secondElement<number>([1,22,3])
console.log(typeof secondElement([1,22,3])) //number
secondElement(['1','22','3'])//Type是String
//或者secondElement<string>(['1','22','3'])
console.log(typeof secondElement(['1','22','3'])) //string
secondElement([true,false])//Type是boolean
//或者secondElement<boolean>([true,false])
console.log(typeof secondElement([true,false])) //boolean
//泛型的类型名称可以任意起
function map<Input,OutPut>(arr: Input[],fn: (arg: Input)=>OutPut): OutPut[]{
return arr.map(fn)
}
const parsed = map(['1','2','3'],(n)=>parseInt(n))
console.log(parsed) //[ 1, 2, 3 ]
泛型函数的限制条件
// Type extends {length: number}表示泛型type继承length属性,类型是number,
// extends {length: number}是一个限制条件,不是扩展条件
// 要求传入的参数必须有length属性
function longest<Type extends {length: number}>(a: Type,b:Type){
return a.length>b.length ? a : b
}
const longArr = longest([1,2],[2,3,4])
const longStr = longest('sre','test')
// const longNum = longest(10,100)//会报错,此时Type是number,不具备length属性
使用受限值
function miniNumLength <Type extends {length: number}>
(obj:Type,minNum: number): Type{
if(obj.length >= minNum){
return obj
}
return {length: minNum} //要求返回泛型类型的代码,返回的是object类型不符合泛型
}
const arr = miniNumLength([1,2,3],6)
指定类型参数
function combine<Type>(arr1: Array<Type>,arr2:Array<Type>): Array<Type>{
return arr1.concat(arr2)
}
combine([1,2,3],[2,3,4])
const arr = combine<string | number>([1,2,3],['2','3','4']) //手动规定参数类型
console.log(arr) //[ 1, 2, 3, '2', '3', '4' ]
编写优秀的通用函数的准则
1.了能的情况下,使用类型参数本身,而不是对其进行约束
2.尽可能少的使用类型参数
3.如果一个类型的参数只出现在一个地方,请重新考虑是否真的需要它
//1.了能的情况下,使用类型参数本身,而不是对其进行约束
function firstEle<Type>(arr: Array<Type>){
return arr[0]
}
function firstEle1<Type extends any[]>(arr: Array<Type>){
return arr[0] //使用类型约束去解析类型,而不是在调用时才解析,此时arr[0]返回的是any类型,不是number
}
const a = firstEle([1,2,3])
const b = firstEle1([1,2,3])
//2.尽可能少的使用类型参数
function filter1<Type>(arr: Type[], fn:(arg:Type)=>boolean){
return arr.filter(fn)
} //这种方式更好
function filter2<Type,Func extends (arg: Type)=>boolean>(
arr:Type[],
func: Func
){
return arr.filter(func)
}
//如果一个类型的参数只出现在一个地方,请重新考虑是否真的需要它
function great <Type extends string>(s:string){
return 'hello' + '' + s
}
function great2 (s:string): string{
return 'hello' + '' + s
}//这种方式更好
函数的可选参数
在写回调函数时,永远不要写一个可选参数,除非打算在不传递参数的情况下调用函数
function f(n?:number){ //n是可选的
console.log(n?.toFixed())
console.log(n?.toFixed(3))
}
function f1(n:number = 100){ //给设置默认值
console.log(n?.toFixed())
console.log(n?.toFixed(3))
}
f(120.45)
f()
function myForEach(arr: any[], callback:(arg:any,index?:number)=>void){
for(let i = 0; i <arr.length; i++){
callback(arr[i])
}
}
myForEach([1,2,34],(a)=>console.log(a))
myForEach([1,2,34],(a,i)=>console.log(a,i))//此时的i可能是undefined 也有可能是number
myForEach([1,2,34],(a)=>console.log(a))
函数重载
根据函数的参数类型和个数去调用方法
function makeDate(timeStamp:number) :Date //重载函数
function makeDate(m:number,d:number,y:number) :Date //重载函数
//实现函数的参数是为了兼容重载函数,不能根据实现参数的个数去要求重载函数
function makeDate(mOrTimeStamp:number,d?:number,y?:number):Date{ //实现函数
if(d&&y){
return new Date(y,mOrTimeStamp,d)
}else{
return new Date(mOrTimeStamp)
}
}
const ab = makeDate(123)
const b = makeDate(1,4,3)
// makeDate(5,9) //会报错: 因为没有需要两个参数的函数重载
console.log(ab)//1970-01-01T00:00:00.123Z
console.log(b)//1903-02-03T16:00:00.000Z
重载签名和实现签名
function fn(x:string):void;
function fn(){
}
// fn() //会报错 外部调用时,只能观察到重载函数的参数,观察不到实现函数的参数个数
fn('hello')
function fn11(x:boolean):void;
function fn11(x:string):void;
function fn11(x:boolean | string){
} //实现函数签名的参数列表要和重载函数参数列表相呼应
function fn11(x:boolean):string;
function fn11(x:string):boolean;
function fn11(x:boolean | string):string|boolean{
return true
} //实现函数的返回值和重载函数的返回值类型相呼应
编写好的重载
在可能的情况下总是倾向于使用联合类型的参数而不是重载参数
function len(s:string):number;
function len(arr:any[]):number;
function len(x: any){
return x.length
}
len('hello')
len([1,2,3])
len(Math.random() > 0.5 ? [1,2,3] : 'hello') //会报错 会返回一个联合类型string | number[]
//完全可以不用写重载
function test(x:string | Array<any>){
return x.length
}
函数内的this声明
interface User{
admin: boolean
}
interface DB{
filterUsers(filter:(this: User)=>boolean): Array<User>
//filterUser方法返回一个User类型的数组,接受一个function类型的参数filter
//filter函数接受一个类型为User的this参数,返回值是boolean
}
const db:DB = {
filterUsers:(filter: (this: User) => boolean) => {
let user1: User = {
admin:true
}
let user2: User = {
admin:false
}
return [user1,user2]
}
}
const admins = db.filterUsers(function (this: User){
console.log(this)
return this.admin //此时this指向的是User,this可以指代传入的参数
})
console.log(admins)
// const admins = db.filterUsers((this: User) => { //会报错,箭头函数内部不能定义this
// console.log(this)
// return this.admin //此时this指向的是User
// })
其他类型
void: 没有返回值的函数的返回值,没有返回值或者返回值语句中没有返回明确的值
TS中,undefined和void是不一样的
object: 任何不是任何基元类型(string,number,bigint,boolean,symbol,null,undefined)的值,不同于{}。也不同于Object,始终使用obbject
unknown: 代表任何值,与any类似,但更安全,因为对unknown值做任何事都不合法
never: 永远不会被观察到的值
Function: 描述了诸如bind,call,apply和其他类型存在于javascript中所有函数值的属性,它还有一个特殊属性: Function类型的值总是可以被调用,这些调用返 回any
参数展开运算符——形参展开
function multiply(n:number,...arr: number[]){ //...arr: 表示不定长度的数字数组
return arr.map(item => item * n)
}
const aa = multiply(10,23,34,56,23,23)
console.log({aa}) //{ aa: [ 230, 340, 560, 230, 230 ] }
参数展开运算符——实参展开
function reaMultiply(arr: Array<number>){
console.log(...arr)//1 2 3 4
}
reaMultiply([1,2,3,4])
const args = [8,5] //类型推断为: 长度为0或者长度不定的数值型数组
const arsgh = Math.atan(...args) // 而Math.atan需要的是两个参数,所以会报错
//修改
const args2 = [6,5] as const
const angle = Math.atan2(...args2)
参数结构
采用参数重构的方法将参数解压到函数主体或多个局部变量中
function sum({a,b,c}:{a:number,b:number,c:number}){
console.log(a+b+c)
}
sum({a:10,b:3,c:9})
//简化一下:
type ABC = {
a:number,
b:number,
c:number
}
//function sum({a,b,c}:ABC)
函数返回的void类型
一个具有void返回值类型的上下文函数类型(type vf = () => void),在实现时,可以返回任何其他的值,但会被忽略
当一个函数字面的定义有一个void的返回类型时,该函数必须不返回任何东西
type VoidFunc = () => void
const f11: VoidFunc = ()=> {return true}
const f2: VoidFunc = ()=> true
const f3: VoidFunc = function (){
return true
}
const f4: VoidFunc = function (): void{
return true //会报错,显式定义返回值为void之后将不允许返回任何值
}
const v1:boolean = f11() // 会报错 不能将boolean分配给void
const v2 = f2()
const v3 = f3()
对象类型
匿名对象/接口命名/类型别名
//第一种写法 匿名对象
function great(person: {
name:string,
age:number
}){
return 'hello' + person.name
}
//第二种: 接口定义
interface Person{
name:string
age: Number
}
function greater(person: Person){
return 'hello' + person.name
}
//类型别名
type Person = {
name:string,
age:number
}
function greated(person: Person){
return 'hello' + person.name
}
属性修改器
属性: 定义对象类型/设置属性是否可选/设置属性是否可写
可选属性
interface PaintOptions{
shape: Shape,
x?:number, //x是可选的
y?:number //y是可选的
}
type Shape = {
}
function paintShape({shape: Shape,x = 0,y = 0}: PaintOptions){
// {shape:Shape,x:number = 0,y:number = 0}在函数中,此时真的number和Shape是x,y和shape的别名
//在结构的时候不要定义类型
console.log(x,y)
console.log(Shape)
}
const shape:Shape = {}
paintShape({shape}) //shape是必填的
paintShape({shape,x:22})
paintShape({shape,x:22,y:22})
只读属性
在被标记为只读的属性,类型检查期间不允许写入其他的值
将一个对象本身设置为readOnly,那么不允许对这个对象本身进行赋值操作,但是其属性是可以修改的
将对象的属性设置为readOnly之后,属性的值也将不可修改
interface SomeType{
readonly prop: string //表示prop是只读属性
}
function doSomethig(obj: SomeType){
// obj.prop='test' //会报错,因为prop是个只读属性
}
interface Home{
readonly resident:{
name:string,
age:number
}
}
function visit(home: Home){
console.log(home.resident.name)
home.resident.name = 'temp'
home.resident.age = 12
}
function evict(home:Home){
// home.resident = {
// name: 'aa',
// age:18
// } 会报错 因为resident本身是不可写的
}
//typeScript在类型赋值时并不会去校验它是不是可写的
interface ReadOnlyPerson{
readonly name: string
readonly age1: number
}
let writablePerson: Person1 = {
name:'test',
age1:18
}
// let readOnlyPerson: ReadOnlyPerson = {
// name:'aa',
// age1:16
// }
let readOnlyPerson: ReadOnlyPerson = writablePerson
console.log(readOnlyPerson.age1) // 18
writablePerson.age1++
console.log(readOnlyPerson.age1) // 19
索引签名
是指属性名和值的可能值的类型,任意的给一个对象定义任何类型的属性,丰富对象的属性类型
interface StringArray{
[index: number]:string//对象StringArray属性的名字是number类型,属性值的类型是string类型
}
const myArray: StringArray = ['a','b']
const secondItem = myArray[0]
interface testString{
[prop: string]: number
}
const testString: testString = {
'name': 12,
'age':18,
temp: 300
}
interface Animal{
name: string
}
interface Dog extends Animal{
bread:string
}
interface NotOkay{
[index: string]: number | string
length: number,
name: string
}
const notOkay: NotOkay = {
x:100,
length:12,
name: 'hello'
}
interface ReadOnlyString{
readonly [index: string]: number
}
const temp: ReadOnlyString = {
age:18
}
temp.age = 15 // 会报错,因为age是不可读的
扩展类型
使用extends扩展
interface BasicAddress{
name?: string
street?:string
city:string
county:string
postalCode:string
}
//向接口BasicAddress中添加unit,用extends将BasicAddress扩展
interface AddressWithUnit extends BasicAddress{
unit: string
}
interface Colorful{
color:string
}
interface Circle{
radius: number
}
//ColorCircle继承Circle和Colorful
interface ColorCircle extends Colorful,Circle{
}
const cc:ColorCircle = {
color:'red',
radius:100
}
交叉类型
用于组合现有的对象类型的
interface Colorful{
color: string
}
interface Circle{
radius: number
}
type CircleColor = Colorful & Circle //使得CircleColor具有Circle和Color的所有属性
function draw(circle: Colorful & Circle){
console.log(circle.radius)
console.log(circle.color)
}
draw({
color:'red',
radius: 100
})
接口和交叉类型的区别
接口使用extends来扩展,交叉类型采用&来扩展
处理冲突: 同名的接口存在时,interface会合并接口,而type不允许有重复的类型,因此interface适用于合并多个类型,type适用于避免重复类型的场景
interface Sister{
name: string
}
interface Sister {
age: number
}
const sister:Sister = {
name:'test',
age:16
}
// type Sister = {
//
// }
// type Sister = {
//
// } //会报错
泛型对象类型
interface Box <Type>{
content: Type
}
const box: Box<string> = {
content:'test'
}
const box1: Box<number> = {
content:12
}
interface Apple{
name:string
}
type AppleBox = Box<Apple>
let ab:AppleBox = {
content:{
name:'232'
}
}
type Box1<Type> = {
contents: Type
}
type OrNull<Type> = Type | null //OrNull可以是Type也可以是null
type OneOrMany<Type> = Type | Type[]
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>> // OneOrManyOrNull的类型可以是Type,Type[],null
type OneOrManyOrNullOrString = OneOrManyOrNull<string>
类型操纵
从类型中创建类型:
1.泛型类型 2.keyof 类型操作符 3.typeof 类型操作符 4.索引访问类型 6.条件类型 7.映射类型 8.模板字面量类型
泛型类型
function identityTest<Type>(arg: Type): Type{
return arg
}
let myIdent: <Type>(arg: Type)=>Type = identityTest //定义泛型函数的方式定义myIdent的类型
let myIdent1: <Input>(arg: Input)=>Input = identityTest //泛型的名字不一定非得用type
let test1:{<Type>(arg: Type): Type} = identityTest //用字面量的形式定义泛型
interface GenerqatorIdentity {
<Type>(arg: Type) : Type
} //泛型接口
let myTest:GenerqatorIdentity = identityTest
interface GenericTest<Type>{
(arg: Type): Type
}
// let myTest1:GenericTest<= identityTest //会报错 因为没有输入泛型的确定类型
let myTest1:GenericTest<string> = identityTest //用户需要输入类型
泛型类
泛型类与泛型接口类似:
class GenericNum<NumType>{
zeroValue: NumType
add:(x: NumType, y: NumType)=> NumType
}
const myGenirc = new GenericNum<number>() // <number>是自定义的
myGenirc.zeroValue = 0
myGenirc.add = function (x,y): number{
return x + y
}
console.log(myGenirc.add(1,2))
泛型约束
function test<Type extends LengthLimit>(arg: Type): Type{
//Type extends LengthLimit 表示传入的参数必须要有length属性
return arg
}
interface LengthLimit{
length: number
}
// test(3)//报错
test('2') //string具有length属性
在泛型约束中使用类型参数
生成一个受其他类型约束的类
function getProperty<Type,Key extends keyof Type>(obj: Type, key: Key){
// key一定属于Type类型的某个key
return obj[key] //返回对象的value值
}
let x = {
a:1,
b:2,
c:3
}
getProperty(x,'a') // 1
// getProperty(x,'m') // 会报错 x对象中没有m
在泛型中使用类类型
function create<Type>(c: { new(): Type}):Type{
return new c()
}
class FeBeeKeeper{
hasMask: boolean = true
}
class ZooKeeper{
namestr: string = 'me'
}
class Animal{
numLegs: number = 4
}
class Bee extends Animal{
keeper: FeBeeKeeper = new FeBeeKeeper()
}
class Lion extends Animal{
keeper: ZooKeeper = new ZooKeeper()
}
function createInstance<A extends Animal>(c: new() => A): A{ //<A extends Animal>表示A必须继承Animal
return new c()
}
const myLion = createInstance(Lion).keeper.namestr
const myBee = createInstance(Bee).keeper.hasMask
keyof 类型操作符
type Point= {
x: number
y:number
}
type P = keyof Point //将Point类型的key作为联合类型付给P:即: 'x'| 'y'
const p1: P = 'x'
const p2: P = 'y'
// const p3: P = 'z' //报错,Point中没有key为z的键值对
type Arrayish = {
[n: number]:unknown
}
type A = keyof Arrayish
const a:A = 0
type Mapish = {
[k:string]:boolean
}
type M = keyof Mapish //M是number | string
const m: M = 1
const m1: M = 'hello'
typeof类型操作符
typeof返回一个类型
const v: number = 1
let s : typeof v //typeof v是number,将number付给S的类型
s = 1
s=3
// s= 'hello' //会报错 s是number类型的变量
//ReturnType<T> 预定义: 要求必须传入一个函数类型
type Predict = (x: unknown) => boolean
type K = ReturnType<Predict>
function f(){
return{
x:10,
y:10
}
}
type Q = ReturnType<typeof f> //不能用typeof标识函数处理的结果
// const q: Q = 100
function MessageBox(){}
let shouldCon: typeof MessageBox
// shouldCon = 100 //会报错 shouldCon是一个()=>void函数类型
//不能写成如下方式: typeof MessageBox()只能修饰变量或者某个对象的某个属性
索引访问类型
通过对象的属性获取值的类型:
当我们使用typeof运算符来获取变量的类型时,对于字符串字面量类型的变量,TypeScript会将其解析对应成对应的字符串字面量类型
interface Person{
name:string
age: number
address: string
}
type Temp = Person['name'] //Type是string类型
const ss:Temp = 'a'
// const aa:Temp = 10 //会报错 aa是string类型的
interface Male{
detail: Person['name' | 'age'] //detail的类型是string | number
}
type Female = Male['detail']
const ff :Female = 'TEST'
const GG :Female = 20
// const LL :Female = true 会报错
type K2 = Person[keyof Person] // K2的类型是:string | number
type K3 = 'name' | 'age'
type I3 = Person[K3] //I3的类型是string | number
const rr: I3 = ''
const rw: I3 = 0
// const rg: I3 = true //会报错,因为rg是string | number
//在索引的时候只能使用类型
type Age = typeof myArr[number]['age'] // Age是number类型
const key = 'age'
type Key = 'age'
// type A4 = Person[key] 会报错,key表示的是值,不是类型
type A4 = Person[typeof key] //此时A4是number类型的,typeof key被解析成了'age',而不仅仅是一个字符串值
type A5 = Person[Key] //此时key是age类型的
const aa: A4 = 7
// const ab: A4 = ''会报错
条件类型
interface Animal {
live: ()=> void
}
interface Dog extends Animal{
woof: ()=>void
}
//Example是number
type Example = Dog extends Animal ? number : string //Dog是否继承自Animal,如果是Example是number类型的反之string
//Example2是string类型
type Example2 = RegExp extends Animal ? number : string
interface IdLabel {
id:number
}
interface nameLabel{
name: string
}
//函数重载的写法
function createLabel(id: number):IdLabel
function createLabel(name: string): nameLabel
function createLabel(nameOrId : number | string): IdLabel | nameLabel
function createLabel(nameOrId : number | string): IdLabel | nameLabel{
return {
id:123
}
}
//利用条件类型的写法
type NameOrId <T extends number | string> = T extends number ? IdLabel :nameLabel
function created<Type extends number | string>(idOrName: Type): NameOrId<Type>{
throw '' // 抛出错误不在执行
}
//nameLabel
let abd =created('typescript')
//idLabel
let dd =created(12)
//nameLabel | idLabel
let cc = created(Math.random() > 0.5 ? 'hello' : 23)
条件类型约束
type MsgOf<T extends {‘message’: unknown}> = T[‘message’] // 表示MsgOf必须有message属性
interface EmailCnt{
message:string
}
type EmailMsgCnt = MsgOf
type MsgOf1 = T extends {message: unknown} ? T[‘message’] : never
interface Dog{
name:string
bark:()=>void
}
const emailCnt: EmailMsgCnt = ‘123’
console.log({emailCnt})
type DogMsgCnt = MsgOf1 //never类型
// const dmsg: DogMsgCnt = ‘HELLO’ //会报错
在条件类型内进行推理
infer关键字:,infer 关键字可以用来从类型中推断出新的类型
type GetReturnType<Type> = Type extends (...args: never)=> infer Return ? Return : never
// infer:关键字在 TypeScript 中,infer 关键字可以用来从类型中推断出新的类型。
// 当你在一个接口(interface)中定义了一个 infer 字段时,它会在接口中添加一个 readonly 的属性,这个属性不会被允许被访问。
// 然后,你可以使用 infer 关键字来从接口中推断出新的类型。
// 例如,如果你定义了一个接口 MyInterface,其中有一个 string 类型的字段 myProperty,
// 你可以使用 infer 关键字来推断出一个新的类型 MyProperty<string>。这个新的类型可以在接口中使用,
// 并且不会对原始的 MyProperty 字段产生影响。
//Num1的类型是number
type Num1 = GetReturnType<() => number>
const n1: Num1 = 12
type Str1 = GetReturnType<string>
const s1 = ''
分布式条件类型
当条件类型作用于通用条件类型时,我们给定一个联合类型,就变成了分布式条件类型
type TwoArray<Type> = Type extends any ? Type[] : never
//分布式
//SerArrOrNumArr的类型是: string[] | number[]
type SerArrOrNumArr = TwoArray<string | number>
let sa:SerArrOrNumArr = [1,2,3]
// let s11:SerArrOrNumArr = 'a' 会报错
let saa:SerArrOrNumArr = ['e']
//非分布式
type ToArrayNoneDis<Type> = [Type] extends [any] ? Type[] : never
//strOrNumArr的类型是(string | number)[]
type strOrNumArr = ToArrayNoneDis<string | number>
类
类成员:类属性,readonly,构造器,方法,Getters/Setters,索引签名
//第一种
class Point{
x: number = 0
y: number = 0
}
const pt = new Point()
pt.x = 1
pt.y = 2
console.log(pt.x,pt.y) // 1,2
//第二种
class Point{
x: number = 0
y: number = 0
constructor(x:number,y:number) {
this.x = x
this.y = y
}
}
const pt = new Point(1,2)
console.log(pt.x,pt.y)
class Okay{
name!:string //当name未进行初始化时 可以使用断言操作符
}
readonly:防止在构造函数之外更改属性的值
readonly:防止在构造函数之外更改属性的值
class Genator{
readonly name:string = 'w'
constructor() {
this.name = 'hello'//加了readonly的属性在构造器中可以二次更改它的值
}
err(){
// this.name = 'err'//会报错, 函数体里也不能更改readonly的属性
}
}
const ge = new Genator()
console.log(ge.name)
// ge.name = 'yh' //会报错,name是只读属性
// 直接初始化属性和用construcor初始化的区别:
// construcor严格意义上来说是由用户进行的初始化,而直接初始化是在定义的时候进行
// constructor是在初始化实例的时候运行,即用new操作符调用的时候,可以传入参数
构造器: constructor
构造函数不能有类型参数,构造函数不能有返回类型注释
class Point1{
x
y
constructor(x: string | number = 0,y:string | number = 0) {
this.x = x
this.y = y
}
}
const p1 = new Point1(1,2)
const p2 = new Point1()
class Base{
key=4
}
class Derived extends Base{
constructor() {
super();
console.log(this.key)
}
}
方法: 类的函数属性
class Point2{
x:number = 10
y: number = 10
scale(n: number): void{
this.x = n * this.x
this.y = n * this.y
}
}
const p3 = new Point2()
p3.scale(10)
console.log(p3.x,p3.y)//100 100
访问器: Getters/Setters
如果类里面的属性存在get,但没有set ,则该属性是只读的
如果没有指定setter参数的类型,那将从getter的返回类型中推断出来
访问器和设置器必须有相同的成员可见性
class C1{
_length = 0
get length(){
return this._length
}
set length(value){
this._length = value
}
}
let c:C1 = new C1()
// c.length() 会报错 get访问器不能调用
console.log(c.length) //0
c.length = 100
console.log(c.length) // 100
class C2{
_size = 0
get size():number{
return this._size
}
//4.3以后支持
set size(value:string|number){
let num = Number(value)
if(!Number.isFinite(num)){ //判断num是不是有穷数字
this._size = 0
}
this._size =num //value的值类型一定是get的返回类型
}
}
let t : C2 = new C2()
t.size = 'hello'
console.log(t.size) //NAN
t.size = 12
console.log(t) //12
继承
implements子句
在TypeScript中,implements
是一种关键字,用于定义一个接口(interface),描述了一组方法的集合,这些方法必须实现该接口。
implements
通常用于以下情况:
在TypeScript中,implements
是一种关键字,用于定义一个接口(interface),描述了一组方法的集合,这些方法必须实现该接口。
implements
通常用于以下情况:
\1. 定义一个接口,但是不想在当前项目中使用它,或者不想让其他开发者使用它。
\2. 需要定义一个接口,但是该接口依赖于其他接口或者类,而自己并不需要实现这些接口或类。
使用 implements
关键字可以有效地定义一个接口,使得其他开发者可以在自己的项目中使用该接口,而自己项目中的开发者则不需要了解该接口的具体实现细节。
例如,可以在一个 TypeScript 文件中使用 implements
关键字来定义一个接口,然后在该接口中声明一些方法,将这些方法标记为 implements
修饰的接口的方法,从而使该接口具有了行为。其他开发者可以在自己的项目中使用该接口,并通过实现该接口来调用接口中声明的方法。
interface C{
ping(): boolean
point?: boolean // 定义可选属性之后 在类里面可写可不写
}
class Middle implements C{ // 在C里面要重写ping方法,因为使用了implements关键字
isShow: boolean = true
ping(): boolean {
return this.isShow
}
}
const m = new Middle()
console.log(m.ping()) // true
extends子句
实现一个类继承另一个类,被继承的类叫做基类,继承的类叫做子类,子类拥有父类的所有属性和方法
class Animal{
move(){
console.log('moving')
}
}
class Dog extends Animal{
woof(times: number){
for(let i=0; i<times;i++){
console.log(i)
}
}
}
const d = new Dog()
d.move()
d.woof(4)
重写方法
TypeScript强制要求派生类总是它基类的一个子类型
//要重写父类的方法,需要保证派生类的参数和父类的兼容
派生类是指在 JavaScript 中,派生类(Subclass)是指一个类(Class)可以继承另一个类的属性和方法,从而扩展或复用其功能。派生类是一种实现继承的方式,其中一个类可以继承另一个类的属性和方法,从而创建一个新的类。派生类可以继承父类的构造函数、方法、属性等等,从而实现代码的重用和复用。
class BaseClass{
greet(){
console.log('base')
}
}
class DerivedClass extends BaseClass{
greet(name?: string) { //要重写父类的方法,需要保证派生类的参数和父类的兼容
if (typeof name === 'undefined'){
super.greet()
}else{
console.log(name?.toUpperCase())
}
}
}
const d1 = new DerivedClass()
d1.greet() // base 在这里greet调用的是派生类的方法,然后派生类再去调用基类的方法
d1.greet('temp') //TEMP
const b:BaseClass = d1 //派生类是基类的一个子类
b.greet() //base
基类和派生类的初始化顺序
顺序:
// 基类的字段被初始化
// 基类构造函数运行
// 派生类的字段被初始化
// 派生类的构造函数运行
class Base1 {
name:string = 'base'
constructor() {
console.log(this.name + 'baseName')
}
}
class DeClass extends Base1{
name:string = 'Derived'
constructor() {
super();
console.log(this.name)
}
}
const d2 = new DeClass() // 输出结果: basebaseName Derived
继承内置类型
内置类型: Array,Map,Err等
class MsgError extends Error{
constructor(m: string) {
super(m);
//明确的设置原型
// Object.setPrototypeOf(this,MsgError.prototype)
}
sayHello(){
return 'hello' + this.message //不报错是因为Error类里面有一个message
}
}
const e = new MsgError('hello')
console.log(e.sayHello()) //hellohello
const msg = new MsgError('hello')
console.log(msg instanceof MsgError) //true
类成员的可见性
public: 公开的默认值,任何对象在任何地方都可以访问 --当前类,当前类的派生类,当前类的派生类的实例都可以访问
protected: 受保护的,只能在当前类或子类中进行访问–派生类可以暴露基类受保护的成员
private: 私有的,只能在当前类中进行访问
class Greeter{
public greet(){
console.log('hello')
}
public sayHello(){
console.log(this.greet())
}
}
const g = new Greeter()
g.greet()
g.sayHello()
class Hello extends Greeter{
constructor() {
super();
this.greet()
}
}
const h = new Hello()
h.greet()
class Greeter{
protected age = 12
private address = 'test'
public greet(){
this.getName()
}
protected getName(){
console.log('hello')
}
public getAddress(){
console.log(this.address)
}
}
class SpecialGreeter extends Greeter{
public age = 18
constructor() {
super();
// console.log(this.address)// 会报错,因为address是private修饰的
}
superHoby(){
this.getName() //’hello‘ ,子类中是可以访问的
}
}
const s = new SpecialGreeter()
// s.getName() //会报错 实例访问基类protected修饰的方法时会报错
console.log(s.age) // 18 派生类可以修改基类的可访问性
// console.log(s.address) 会报错
//跨实例访问私有属性
class A{
private x: number = 10
public sameAs(other: A){
return other.x === this.x // 在类里面可以访问其他类的私有属性
}
}
类的静态成员
在TypeScript中,类的静态成员(static members)是指在类定义中使用关键字static
定义的成员,只能在类内部访问,不能被实例化对象的外部访问。
静态成员可以分为两种类型:
静态字段(static fields):定义在类上的字段,只能被类本身访问,不能被实例化对象的外部访问
静态方法(static methods):定义在类上的方法,只能被类本身访问,不能被实例化对象的外部访问。
// 不与类的特定实例相关联,可以通过类的构造函数对象本身去访问
// 访问静态成员不需要实例化 直接用类的名字和属性访问符就可以
// 特殊的静态成员名称不安全,避免使用name,length,call等
// TS没有静态类的概念,因为有函数和普通对象
// 静态成员也可以用private,protected,public修饰
// 静态成员是会被继承的
class MyClass{
private static x = 0
static printX(){
console.log(MyClass.x)
}
}
MyClass.printX() // 0
// console.log(MyClass.x) 会报错 因为在x是一个私有的静态成员,外部不能访问
class Base2{
static getGreet(){
return 'hello, world'
}
}
class Dev2 extends Base2{
myGreet = Dev2.getGreet() // 静态成员是会被继承的
}
类里的static区块
class Foo{
static #count = 0 //#表示这个属性是私有的
get count(){
return Foo.#count
}
static {
try{
const lastInstance = {
length: 100
}
Foo.#count = lastInstance.length
}
catch {
}
}
}
// Foo.count 会报错 count是内专用
类运行时中的this
ts只是在开发时做一些类型检查,并不会影像JS运行时的行为
使用箭头函数,会浪费更多的内存,因为每个类实例都将有自己的副本,并且不能再派生类中调用super获得父类的方法,因为再原型链中没有入口可以获得基类的方法
使用this参数,每个类定义只有一个函数被分配,而不是每个实例都会创建一个函数,基类的方法仍然可以通过super来调用
class MyClass1{
name = 'myclass'
// getName(){
// return this.name
// }
// getName = () => this.name
getName(this: MyClass1){ //该方法接收一个参数 this,表示当前对象(即 MyClass1 类的实例),并不会影像结果。
return this.name
}
}
const c1 = new MyClass1()
console.log(c1.getName()) // myclass
const obj = {
name: 'obj',
getName: c1.getName
}
console.log(obj.getName()) // obj
//想让getName一直返回当前类的名称,可以将getName定义成箭头函数
// 或者可以用this当作参数
this类型
this类型
// this会动态指向当前类的类型
class Box{
content: string = ''
set(value: string){
this.content = value
return this
}
}
class ClearableBox extends Box{
clear(){
this.content = ''
}
}
const a = new ClearableBox()
const f = a.set('hello')
console.log(f) // this指向的是ClearableBox { content: 'hello' }
基于类型守卫的this: this is Type
结合if判断使用,缩小类型范围
class FileSystemObject{
isFile(): this is FileRep{
return this instanceof FileRep
}
isDirectory(): this is Directory{
return this instanceof Directory
}
isNetworked(): this is Networked & this{
return this.networked
}
constructor(public path: string,private networked: boolean) {
}
}
class FileRep extends FileSystemObject{
constructor(path: string, public content: string) {
super(path,false);
}
}
class Directory extends FileSystemObject{
children: Array<FileSystemObject>
constructor() {
super('',false);
this.children = []
}
}
interface Networked{
host:string
}
const fso: FileSystemObject = new FileRep('./bar/test','foo')
if(fso.isFile()){
//const fso: FileRep
console.log(fso.content)
}else if(fso.isDirectory()){
//const Directory: FileRep
console.log(fso.children)
}else if (fso.isNetworked()){
//const isNetworked
console.log(fso.host)
}
// BoxTemp是一个泛型类,它具有一个可选的value属性来存储任意类型的值。
// hasValue()是一个方法,它返回一个布尔值,指示BoxTemp实例是否具有非undefined的value属性。
// 如果value不是undefined,则说明实例具有值,返回true;否则,返回false。
// this is {value: T}是一个类型谓词,用于在返回值类型中指定更具体的类型信息。
// 它表示如果类型推断使用此方法后返回true,那么该实例将被视为具有特定类型{value: T}。
// 简而言之,BoxTemp类允许创建可以包含任何类型的值的实例,并且提供了一种检查实例是否具有值的方法。
class BoxTemp<T>{
value?: T
hasValue(): this is {value: T}{
return this.value !== undefined
}
}
const box1 = new BoxTemp()
box1.value = 'tets'
if(box1.hasValue()){
console.log(box1.value)
}
参数属性
在类的构造函数中,可以结合修饰符定义参数,这些定义的参数既是构造函数的参数又是类的成员,而且具有类的修饰符的属性
class Params{
constructor(public readonly x: number, protected y: number,private z: number) {
}
}
const p: Params = new Params(12, 13, 14)
console.log(p.x)
console.log(p) //Params { x: 12, y: 13, z: 14 }
// p.x= 15 会报错,因为x是只读属性
// console.log(p.y) 会报错 因为protected修饰的属性是不能在实例访问的
// console.log(p.z) 回报错 因为private定义的属性是私有的,不能在实例访问
类表达式
类表达式不需要名字,类似于匿名类
const SomeClass = class <Type>{
content:Type
constructor(value: Type){
this.content = value
}
}
const h = new SomeClass(12)
console.log(h.content) // 12
抽象类和成员
在TypeScript中,抽象类(Abstract Class)是一种特殊的类,用于定义其他类的基础。抽象类可以包含普通类(通常被称为抽象类)和静态成员(通常被称为静态成员)。
普通类可以包含任意数量的成员,包括抽象方法和普通成员。抽象方法是只能在子类中实现的成员,它们必须使用关键字abstract
来定义。普通成员则是指可以在子类中继承的成员,它们没有特殊的访问权限。====
抽象类还可以包含一个构造函数和一个析构函数。构造函数用于初始化对象,而析构函数则用于在对象被销毁时进行清理工作。
在TypeScript中,抽象类和成员的概念对于设计类和接口非常重要。通过使用抽象类和成员,可以确保子类具有相同的接口,并且可以实现抽象方法。这有助于在项目中实现统一、可维护的接口,并促进代码的可读性。
abstract class BaseMiddle{
abstract getName():string //定义抽象方法的时候不用实现,意义在于让别人去实现
printName(){
console.log(this.getName())
}
}
// const b = new BaseMiddle() 抽象类不可以实例化
class DerivedBase extends BaseMiddle{
getName(): string {
return "world";
}
}
const b1 = new DerivedBase()
console.log(b1.getName()) // world
function greet(ctor: new () => BaseMiddle){
//构造签名
const instance = new ctor()
instance.printName()
}
greet(DerivedBase)
类之间的关系
类可以相互替代,当两个类的成员属性和类型是一致的,即使值不同,也可以互相替代
空的类相当于一个普通的函数,传入任何值都是可以的
class Point11{
x = 0
y = 0
}
class Point22{
x = 0
y = 1
}
const point : Point11 = new Point22() //类可以相互替代,当两个类的成员属性和类型是一致的,即使值不同,也可以互相替代
class Person{
name:string = ''
age:number = 12
}
class Employee{
name:string='ss'
age:number = 12
salary: number = 10
}
const per: Person = new Employee() // TS内部做了类的类型判断
class Empty{
}
function fn(x: Empty){
}
fn(window)
fn({})
fn(()=> {})
fn(12)