接口和应用场景
接口:interface 其实就是一种定义对象类型的类型
接口应用场景:
- 一些第三方包或者框架底层中会有大量的接口类型
- 提供方法的对象类型的参数使用
- 为多个同类别的类提供统一的方法和属性声明
定义接口(其实感觉好像跟java中的class有那么一丢丢像)
interface Product{
name:string
price:number
account:number
}
//声明
let p:Product={
name:"phone",
price:100,
account:10
}//如果不根据接口中定义的属性和类型去声明就会报错
继承接口
//继承
interface dog extends Pet{}//可以拓展父类的方法
class ArrayList implements List{}//会自动生成方法
可索引签名
interface Product{
[x:string]:any
price:number
}
//[x:类型]固定写法记住!
type A = Product["price"]
//可以直接拿到price的类型
//接口同名会合并
//这下面有两行代码先记住,等到泛型再理解
type AllKeys<T>=T extends any?T:never
type PKeys=AllKeys<keyof Product>
//any,unknown,undefined可以接受undefined
//any,unknown,null可以接受null
//rest参数
function info(name:string,age:number,...rest:any){}
//rest参数可以一直给
let data:unknown=undefined
interface和type区别
type和接口类似,都用于定义接口
但是区别:
1.定义类型不同
interface只能定义对象类型或者接口当名字的函数类型
type可以定义任何类型,包括基础类型、联合类型,交叉类型,元组
2.接口可以extends一个或者多个接口或类,也可以继承type,但是type类型没有继承功能,但一般接口继承 类和type的应用场景很少见
3.type交叉类型&可让类型中的成员合并成一个新的type类型,但接口不能交叉合并
type Group={groupName:string,memberNum:number}
type GroupInfoLog={info:string,happen:string}
type GroupMemeber=Group&GroupInfoLog//type交叉类型合并
let data:GroupMember={
groupName:"001",memberNum:10,
info:"集体爬山",happen:"中途有组员差点滑落,有惊无险',
}
export{}
4.接口可以合并声明
定义两个相同名称的接口会合并声明,但是type会报错
元组
- 定义时每个元素的类型都确定
- 元素值的数据类型必须事当前元素定义的类型
- 元素值的个数必须和定义时个数相同
TS数组和数组元素怎么样同时为只读?
const account=[10,40,50,60,90] as const
account[1]=100;
//as const是关键,让别人改不了内部的数据
可变元组
let customers:[string,number,string,...any[]]=[]
//可变元组的关键就是那个any
//可变元组的解构
let [custname,age,address,...rest]:[custname_:string,age_:number,addr:string,...any[]] = [...]
类
定义:拥有相同属性和方法的一系列对象的集合
class People{
name!:string;
age!:number;
addr!:string;
static count:number=0;//静态对象是可以由所有的该类型通用且相同,且不由变量赋值改变。
//constructor(_name:string,_age:number,_addr:string){
// this.name=_name;
// this.age=_age;
// this.addr=_addr;
// People.count++
doEat(){}
doStep(){}
}//一定是要赋值的,如果你不想写构造函数就直接加!
TS单例模式的实现
//第一种实现方法,不好的地方就是只要跑起来,就创建好,有时候你不用它也会自动创建,浪费时间空间
class DateUtil{
static dateUtil = new DateUtil()//立即创建单件模式
private constructor(){}
formarDate()
diffDateByDay(){}
}
//静态对象的执行时机,会最先执行,且只会执行一次,并不会重复执行
//所以推荐第二种
class DateUtil{
static dateUtil:DateUtil
private constructor(){
console.log("创建对象...")
}
static getInstance(){
if(!this.dateUtil){
this.dateUtil = new DateUtil()
}
return this.dateUtil
}
}
//这种就可以自己控制,你想要创建对象,你就调用构造函数
const dateUtil1 = DateUtil.getInstance()
const dateUtil2 = DateUtil.getInstance()
getter和setter的存在意义
在加入一些属性的时候,可以添加一些限制
不要在构造函数里写这些方法,因为构造函数是对所有的属性,属性一多就会显得很乱。所以对于单个属性,设置get和set方法。
不同实例的方法声明会在不同的空间
class DateUtil{
static dateUtil:DateUtil
private constructor(){
console.log("创建对象...")
}
static getInstance(){
if(!this.dateUtil){
this.dateUtil = new DateUtil()
}
return this.dateUtil
}
doEat(who:string,where:string){
console.log(`who:${who},where:${where}`)
}
}
const dataProp1 = Object.getOwnPropertyDescriptor(People.prototype,"doEat")
const dataProp2 = Object.getOwnPropertyDescriptor(People.prototype,"doEat")
console.log(dataProp1 === dataProp2)
//打印出false,说明开辟了新的空间
方法拦截器
用于方法调用前,或者之后的处理
class StringUtil{
static trimSpace(str:string){
return str.replace(/\s+/g,"")
}
}
const dataProp1 = Object.getOwnPropertyDescriptor(People.prototype,"doEat")
const targetMethod = dataProp1!.value//这行代码可以取到他的方法
dataProp1!.value = function(...args:any[]){
args = args.map((arg)=>{
if(typeof arg === "string") return StringUtil.trimSpace(arg)
return arg
})
console.log("前置拦截。。。")
targetMethod.apply(this,args)
console.log("后置拦截")
}
Object.defineProperty(People.prototype,"doEat",dataProp1!)
let p = new people("peter",23,"公主坟")
p.doEat("张 三","王 家 路")
export{}
//最终打印结果是张三,王家路