函数的重载引言
编写一个add函数,希望对字符串和数字类型进行相加,如何编写?
type AddType = number | string
function add(a1: AddType,a2: AddType){
return a1 + a2;
//直接这样会报错,因为不能确定具体是哪个类型进行相加
}
改进:但是会存在很多缺陷(多个if判断)
函数重载的应用
函数的名称相同,但是参数个数、参数类型不同的 几个函数,就是重载
//函数的声明
表示:2个参数是number类型、返回值也是number类型
function add(num1: number,num2: number):number;
function add(num1: string,num2: string): string;
//函数的实现(这里实现具体函数实现)
function add(num1: any,num2: any):any{
if(typeof num1 === 'string' && num2 === 'string'){
//如果是字符串,我希望得到长度的结果
return num1.length + num2.length
}
return num1 + num2
}
//函数的使用
const result = add(20,30)//使用第一个add方法
const result = add("123","cba")//匹配第二个
add({name:"why"},{age:18})//这样是不行的
//在函数的重载中,实现函数是不能被调用的
函数重载的案例
定义一个函数,可以传入字符串或数组,获取她们的长度
方案一:使用联合类型来实现
//任意类型的数组
function getLength(args: string | any[]){
return args.length
}
console.log(getLength("abc"))
console.log(getLength([123,123,123]))
方案二:使用函数重载来实现
function getLength(args: string):number;
function getLength(args: any[]):number;
function getLength(args:any):number{
return args.length
}
console.log(getLength("abc"))
console.log(getLength([123,123,123]))
在可能的情况下,我们尽量选择联合类型来实现
类的使用
编程范式:面向对象编程 和 函数式编程(JS)
class Person{
//属性和方法,必须初始化,不然会报错
//初始化的两种方式(构造器和直接赋值)
name:string = ""
age:number
//构造器用来初始化
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
eating(){
console.log(this.name + "eating")
}
}
//实例化的时候会自动执行constructor
const p = new Person("why",18)
console.log(p.name)
console.log(p.age)
export {}
类的继承
我们发现Student和Teacher有一些共有属性
将这些共有的属性、方法存储在Person
class Person{
name: stirng = ""
age: number = 0
eating(){
console.log("eating")
}
}
-----------------------------------------
class Student extends Person{
sno: number = 0
studying(){
console.log("studying")
}
//extends继承Person后,就可以使用了
}
class Teacher extends Person{
title:string = ""
teaching(){
console.log("teaching")
}
}
const stu = new Student()
console.log(stu.name)//可以调用继承的方法属性
console.log(stu.age)//是有Person中的属性方法的
stu.name = "why"//也可以自己修改自己想要的属性
class Person{
name: stirng
age: number
eating(){
console.log("eating")
}
constructor(name: string,age: number){
this.name = name;
this.age = age;
}
}
class Student extends Person{
sno: number
//初始化工作应该在new Studnet的时候完成
//此时就需要使用构造器来初始化
constructor(name: string,age: number,sno?: number){
//必须写在第一行
super(name,age)//调用父类的构造方法,这些属性在父类中就初始化过了
//sno? 表示可选,可以传 也可以不传
this.sno = sno
}
studying(){
console.log("studying")
}
//对父类的方法不满意,我自己重写
eating(){
//调用父类代码,使用super
super.eating()//位置随便写
console.log("Studnet-eating")
}
}
const stu = new Student()
类的多态
//存放动物的共有属性、犯方法
class Animal{
action(){
console.log("animal action")
}
}
class Dog extends Animal{
//对父类的action不满意,我自己重写一个
action(){
console.log("Dog running")
}
}
class Fish extends Animal{
action(){
console.log("Fish swimming")
}
}
//要求传入一个Animal
function makeActions(animals:Animal[]){
animals.forEach(animal => {
//这里会执行 重写之后的方法,并不是传来方法中 继承的父类的方法
animal.action()
})
}
//因为全部继承Animal,所以算Animal类型
makeActions([new Dog(),new Fish()])
new Dog()传给Animal[] 中的实际操作:
const animal:Animal = new Dog() 左父右子;animal.action()表示 父类引用(类型) 指向子类对象
多态的目的:为了写出更加具备通用性的代码。
类的成员修饰符
public的使用
class Person{
//不写修饰符默认就是public
public name: string = ""
}
const p = new Person()
console.log(p.name)//不在一个类中,也可以拿到属性
private的使用
class Person{
private name: string = ""
}
const p = new Person()
console.log(p.name)//这样是取不到的
------------------------------------------
class Person{
private name: string = ""
getName(){
return this.name
}
setName(newName){
this.name = newName
}
}
const p = new Person()
console.log(p.getName())//通过 对外暴露的getName函数来访问
p.setName("哈哈")//可以修改了
protected的使用
在类内部和子类中可以访问
class Person{
protected name: string = ""
}
class Student extends Person{
getName(){
//子类Student可以使用父类中的protected的属性、方法
return this.name
}
}
const stu = new Student()
console.log(stu.getName())
console.log(stu.name())//访问不到的,因为不在他的子类中
只读属性readonly
class Person{
readonly name: string
readonly friend: Person
//只读属性可以在构造器中赋值,赋值后不可以修改
constructor(name: string,friend:Person){
this.name = name
this.friend = friend
}
}
//构造器中赋值
const p = new Person("why",new Person("kobe"))
console.log(p.name)//只可以访问
p.name = "123"//会报错,不允许修改
if(p.firend){
//readonly仅仅对friend只读
但是如果他是对象类型,他里面的属性仍然是可以修改的
p.friend.age = 30
}
和js中这种形式是一样的道理:
const info = {name:"why"}
info.name = "123"
getters/setters
class Person{
private name: string
constructor(name:string){
this.name = name
}
}
const p = new Person("why")
console.log(p.name)
p.name = "123"
------------------------------------------
其实上方那样写,对私有属性操作会很麻烦的,我们应该使用
setters/getters来对数值进行初始化和使用
class Person{
//私有属性我们习惯用 _ 下划线开头
private _name:string
constructor(name: string){
this._name = name
}
//访问器setter/getter
//setter
set name(newName){
this._name = newName
}
//getter
get name(){
return this._name
}
}
const p = new Person("why")
p.name = "coderwhy"//可以直接修改了
console.log(p.name)
当私有属性很多的时候,推荐使用访问器setter/getter,而不是使用setName和getName
类的静态成员
class Student{
//静态属性,可以通过类,直接调用,不用new 实例化了
static time: string = "20:00"
static attendClass(){
console.log("去学习~")
}
}
console.log(Studnet.time)
Studnet.attendClass()
抽象类
//类型指定为Shape表示 必须传入返回getArea方法的才可以
function makeArea(shape: Shape){
return shape.getArea()
}
abstract class Shape{
//这种没有实现体的函数,最好写成抽象函数
//getArea(){}
//抽象方法必须存在抽象类中,也不能被实例化
//抽象类中的抽象方法必须被子类实现
abstract getArea()
}
//矩形
class Rectangle extends Shape{
private width:number
private height:number
constructor(width:number,height:number){
super()
this.width = width
this.height = height
}
getArea(){
return this.width * this.height
}
}
//圆形
class Circle extends Shape{
private r: number
constructor(r:number){
super()
this.r = r
}
getArea(){
return this.r * this.r * 3.14
}
}
const rectangle = new Rectangle(20,30)
const circle = new Circle(10)
//传入本身函数,然后makeArea再返回本身函数中的getArea方法的返回值
console.log(makeArea(rectangle))
console.log(makeArea(circle))
使用抽象类
abstract class Shape{
//这种没有实现体的函数,最好写成抽象函数
//getArea(){}
//抽象方法必须存在抽象类中,也不能被实例化
//抽象类中的抽象方法必须被子类实现
abstract getArea()
}
//子类继承Shape
class Circle extends Shape{
private r: number
constructor(r:number){
super()
this.r = r
}
//必须实现getArea抽象方法
getArea(){
return this.r * this.r * 3.14
}
}
类的类型
class Person{
name: string = "123"
eating(){
}
}
const p = new Person()
//将Person类 作为类型注解
const p1:Person = {
name:"why"
//类中有什么属性,这里就必须要写相应的属性、方法
eating(){
}
}
//要求使用该函数的时候,传入一个Person类的实例
function printPerson(p:Person){
console.log(p.name)
}
printPerson(new Person())//类实例可以
printPerson({name:"123",eating:function(){}})//直接这样解构的写,也可以
接口的声明
声明对象类型
1.通过类型别名,声明对象类型
type InfoType = {name:string,age:number}
const info:InfoType = {
name:"why",
age:18
}
------------------------------------------
2.通过接口interface,声明对象类型
//规范:一般对接口名 前面多加一个 I
interface IInfoType{
readonly name:string //只读属性
age:number
friend?:{
name:string
}//可选属性
}
const info: IInfoType = {
name:"why",
age:18
//可以写,可以不写。写了就必须要写name属性
friend:{
name:"kobe"
}
}
console.log(info.friend?.name)
索引类型
//通过interface来定义索引类型
interface IIndexLanguage{
//必须是一个数字类型的key,他的value是string类型
[index:number]:string
}
//索引必须是number类型,值是string类型
const frontLanguage:IIndexLanguage = {
0:"HTML",
1:"CSS",
2:"JavaScript",
3:"Vue"
}
//key是string类型,value是number类型
interface ILanguageYear{
[name:string]:number
}
const languageYear: ILanguageYear = {
"C":1972,
"Java":1995,
"JavaScript":1996,
"TypeScript":2014
}
函数类型
type CalcFn = (n1:number,n2:number)=>number
//传入3个参数,2个number类型,1个CalcFn类型函数
function calc(num1:number,num2:number,calcFn:CalcFn)
//add为CalcFn函数类型,即传入的参数为2个数字类型,返回值为数字类型的函数
const add: CalcFn = (num1,num2)=>{
return num1 + num2
}
calc(20,30,add)
通过接口来定义 函数类型
接口里面存储的形式就是 key:value的这种形式
接口函数类型 少用,最好还是用type,因为阅读性不是很好
interface CalcFn{
//平替 (n1:number,n2:number)=>number
//接口中,函数类型的写法,可调用接口
//冒号左边:参数,右边是返回值
(n1:number,n2:number): number
}
function calc(num1:number,num2:number,calFn:CalcFn){
return calcFn(num1,num2)
}
const add:CalcFn = (num1,num2)=>{
return num1+num2
}
calc(20,30,add)
接口的继承
interface ISwim {
//swimming的类型是一个函数类型
swimming: () => void
}
interface IFly {
flying: () => void
}
//接口可以实现多继承
interface IAction extends ISwim, IFly {
}
//因为action的类型是IAction,所以必须要写继承过来的swimming和flying
const action: IAction = {
swimming() {
},
flying() {
}
}
交叉类型
前面我们说过一种 组合类型的方式:联合类型
type WhyType = number | string
type Direction = "left" | "right" | "center"
另一种组合类型的方式:交叉类型
//即要符合number类型,又要符合string类型
type WType = number & string
//不过这种情况是不存在的never
interface ISwim {
swimming: () => void
}
interface IFly {
flying: () => void
}
type MyType1 = ISwim | IFly
type MyType2 = ISwim & IFly
const obj1:MyType1 = {
//只需要满足任意一个类型
}
const obj2:MyType2 = {
//两种类型要同时满足,这就是交叉类型
swimming(){},
flying(){}
}
接口的实现
interface ISwim(){
swimming: ()=> void
}
interface IEat {
eating: ()=> void
}
//实现接口
class Animal{
}
//继承:只能实现单继承
//实现:实现接口,可以实现多借口
class Fish extends Animal implement ISwim,IEat{
//实现接口中的方法
swimming(){
console.log("Fish swimming")
}
eating(){
console.log("Fish eating")
}
}
当我们编写一些公共的API(接口),我们可以实现'面向接口编程'
function swimAction(swimable:Fish){
swimable.swimming()
}
此时这里必须传入一个FIsh类型,才能够游泳
但是我希望所有的类型应该都可以游泳,这里相当于是限制死了
swimAction(new Fish())
-----------------------------------------改进
//类型为 ISwim接口类型
function swimAction(swimable:ISwim){
swimable.swimming()
}
//于是,所有 实现接口的类 对应的对象,都可以传入
swimAction(new Fish())
swimAction(new Person())
interface 与 type 的区别
定义非对象类型:推荐使用type,比如联合类型、一些function
定义对象类型:interface可以重复的对某个接口来定义属性和方法
interface IFoo{
name:string
}
//两个接口名相同,TS允许定义两个相同名字的接口
interface IFoo{
age:number
}
//这里两个属性会同时被结合在IFoo中
const foo: IFoo = {
//虽然分开写了两个IFoo,但是必须要实现他们俩所有的属性
//说明最终会汇聚在最终的IFoo中
name:"abc",
age:18
}
但是type就不可以这样写,type取的是别名,不允许多个相同的别名
官方文档指出;能使用interface就不要使用type。
字面量赋值
interface IPerson{
name:string
age:number
height:number
}
const p: IPerson = {
name:"why",
age:18,
height:1.88,
//我在这里多加了一个属性,但是类型限制是IPerson接口类型
address:"南京"
//其实这里是不可以加的,会报错
}
但是!!
const info = {
name:"why",
age:18,
height:1.88,
address:"南京"
}
//这样是对的
const p: IPerson = info
console.log(info)//info和p共同输出4个属性
console.log(p)
console.log(p.address)
//实际上是取不到的,被擦出了。但是能看到,更加灵活
因为info没有类型限制,类型推导的时候是 对象 类型。
然后将对象类型的info,赋值给了p。本质上是将对象的引用赋值过来了。此时p会进行 freshness擦除操作,类型检测时,当我们info赋值给p的时候,他会去除掉info中多余的属性,擦除后如果依然满足类型限制,那么我还是允许你满足条件的。
擦除只擦除不符合原来的数据,如果你本身就少数据,那你还是不符合的。
TS枚举类型
枚举类型是TS特有的,JS中没有。
//Direction是枚举类型 是一个类型
//枚举出 可能存在的值,当年我们用的是type
enum Direction {
//枚举值一般是大写的,可以是string、number
LEFT,
RIGHT,
TOP,
BOTTOM
}
//传入一个枚举类型参数
function turnDirection(direction:Direction){
}
//必须写枚举类型的参数
turnDirection(Direction.LEFT)
enum和never的配合使用
enum Direction {
LEFT,
RIGHT,
TOP,
BOTTOM
}
function turnDirection(direction:Direction){
switch(direction){
case Direction.LEFT:
console.log("向左")
break;
case Direction.RIGHT:
console.log("向右")
break;
case Direction.TOP:
console.log("向上")
break;
case Direction.BOTTOM:
console.log("向下")
break;
//never:因为一定是这四个类型,如果少写一个TOP,就会报错
//必须把上面4个枚举全部穷举完,少写一个就报错
default:
const foo:never = direction
break;
}
}
turnDirection(Direction.LEFT)
turnDirection(Direction.RIGHT)
turnDirection(Direction.TOP)
turnDirection(Direction.BOTTOM)
枚举类型的值
enum Direction {
LEFT,
RIGHT,
TOP,
BOTTOM
}
实际上默认 等价于
enum Direction {
LEFT = 0,
RIGHT = 1,
TOP = 2,
BOTTOM = 3
}
字符串类型
enum Direction {
LEFT = "LEFT",
RIGHT = "RIGHT",
TOP = "TOP",
BOTTOM = "BOTTOM"
}
也可以自己自行修改
枚举类型补充
let name: string = "abc"
//枚举类型 获取枚举值
let d: Direction = Direction.LEFt