TypeScript中的类型声明和Class类

9/20/2022

# 什么是TypeScript

TypeScript是微软开发的一种静态类型的编程语言,它是 JavaScript 的超集 ; TypeScript 设计目标是开发大型应用, 它可以编译成纯 JavaScript, 编译出来的 JavaScript 可以运行在任何浏览器上。

# TypeScript简易环境搭建

  1. 开发工具使用vsCode编辑器
  2. 新建一个项目文件夹( 例:myStudyTs )并加入到VsCode的工作区
  3. 在myStudyTs根目录终端执行 tsc --init, 会自动创建一个tsconfig.json文件,此文件是ts编译的一些配置项
  4. 根目录新建 index.ts文件 , 用于后续的TS编写
  5. 根目录新建 js 文件夹,并在 js 文件下新建 index.js 文件 ( 此步骤可省略, 第六步的配置后,ts 编译会自动生成此文件夹 )
  6. 将 tsconfig.json 中的 "outDir": "./" 注释解开改为 "outDir": "./js" , ts 文件编译之后会自动在 tsconfig.json 同级目录新建 js 文件夹,并在 js 文件夹下创建编译后的 index.js
  7. 根目录新建 index.html文件,在 html 文件中链接入编译后的 index.js , 在浏览器中打开html
  8. 使用 VsCode 监视 TypeScrip t的运行,目的是在 index.ts 编写之后保存就自动编译,无需再使用 node 命令编译 ts 文件 ,步骤: VsCode ==>终端(T) ==>运行任务 ==>typescript ==>tsc:监视 - tsconfig.json myStudyTs

至此,TypeScript简易环境就搭建好了,保存 index.ts 之后刷新浏览器页面, 即可在浏览器控制台可以看到在index.ts中打印的结果

# TypeScript类型声明

# 一、number类型

let num:number = 18
num = "18" // 报错,不能将类型string 分配给number

let num2:number = 0xf00d;
let num3:bigint = 100n 

# 二、string类型

let str:string = "hello world"
str = "abc"

//指定函数参数类型
let fn = function(n1:number,n2:string):string|boolean{
    //do something
    return "hello"
}

# 三、boolean类型

let flag:boolean = true 
flag = false
flag = 123 //报错,不能将类型number,分配给boolean

# 四、字面量

let a:10
a = 11 //报错,上面已经指定了a的值和类型,都不能更改

let sex:"male" | "female"  //可配合联合类型使用
sex = "male";
sex = "female"
sex = "hello" //报错,只能为sex声明时指定的值和类型

let ran:boolean | string // ran可以是boolean和string类型的任意值

# 五、any类型

//any表示任意类型的值,相当于对此变量关闭了ts的类型检测,尽量减少any的使用
let num:any = 10  //显式any     
num = "hello"
num = true
num = null

let num2; //隐式any ,声明变量时没有声明数据类型也没有值

# 六、unknown类型

//unknown表示未知类型,实际上就是类型更加安全的any
let e:unknown;
e = true
e = 18

//any类型的变量可以赋值给任意类型的变量 ( 因此any不安全 )
let ay:any;
let str:string
str = ay //并不会报错,显然使用any不安全

//unknown则不会有上面的问题,更加安全
e = "hello"
let str2:string
str2 = e //报错,unknown类型的变量不能复制给其他类型的变量,即使值的类型相同也不可以

//unknow类型的变量想要赋值给其他类型的变量可以通过 类型断言as或者 < >实现
str2 = e as string  //告诉ts变量e就是string类型的的,与str2的类型相同
str2 = <string>e  //同上

# 七、void类型

// void表示空,以函数为例,表示没有返回值的函数(return undefined和null除外)
function fun():void{
   console.log(1)
}

# 八、never类型

// never表示永远不会返回任何类型结果,可以用来抛出错误,了解即可
function fun():never{
   console.log(1)
   throw new Error("报错了")
}

# 九、object类型

let obj1:{ //注:const关键字声明变量必须要有初始值
    name:string,
    age?:number // ?表示age属性是可选的
}
obj1 = {sex:"male"} //报错,obj中的属性必须符合声明时的obj的结构

//更加灵活的声明方式
let obj2:{
    name:string,
    [propName:string]:string //表示obj2对象中可以添加任意多个满足:属性名类型为字符串,值的类型为string的键值对
}

let obj3:{
    name:string,
    [propName:string]:number //报错,规定任意值的类型为number后会与name的数据类型冲突
}

# 十、array类型

// 数值数组( 两种声明方式 )
let numArr:number[];
let numArr:Array<number>;
numArr = [1,2,3]
numArr = [1,2,"hello"] //报错

//字符串数组
let strArr:string[];
let strArr:Array<string>;
strArr = ["18","hello"]

//布尔值数组...同理
let booArr:boolean[];
let booArr:Array<boolean>;
strArr = [true,true,false]

//指定混合类型数组
let mixArr:(string | number)[]
mixArr = [18,123,"hello"]

# 十一、tuple元组 类型

//元组表示长度固定的数组,且每个索引位置的数据类型要与声明时的索引位置对应的数据类型相同
let tupleArr:[number,string]
tupleArr = [123,"hello"]
tupleArr = ['hello',18] //报错,与声明时对应位置上的数据类型不相同
tupleArr = [123,"hello","111"] //报错,长度与声明时的长度不相同

# 十二、enum枚举 类型

// enum可以列举所有可能的值
enum Gender{
    male = 0,
    famale = 1
}
let obj:{
   name:string,
   sex:Gender //表示sex可能的值为0或1
}
obj = {
    name:"你好",
    sex:Gender.male
}

# 十三、type类型别名 类型

type myType = string //相当于给string类型起了一个名字叫myType,不过这样没有意义
let str:myType = "hello"

type myType1 = 1|2|3 //表示可能的值为1 2 3的起名叫myType1
let num1:myType1  //这样num1可能的值也是1 2 3
num = 1

type myType2 = {
    name:string,
    age?:number,
    [propName:string]:any
}
let obj:myType2 = {
    name:"孙悟空",
    sex:"男"
}

# 十四、interface接口 类型

//interface 用来定义一个类的结构,也可以用于类型声明(描述对象或函数的结构)
interface SetPoint {
  (x: number, y: number): void;
}
//接口可以使用相同的名字,同名的接口会合并,这也是"接口"不同于"类型别名"的一个地方
interface myInterface{
   name:string
   age?:number
   sayHello():void
}
interface myInterface{
    sex:string
}
//两个myInterface进行了合并
let obj:myInterface = {
   name:"孙悟空",
   sex:"男"//必须要有此属性
   sayHello:function(){
       console.log(this)    
   }
}

//接口继承
interface searchParams{
    name?:string //name属性可选
    age:number
}
interface params extends searchParams{ //可继承多个,逗号隔开
    page:number
    rows:number
} 
let obj:params = {
    page:1,
    rows:20,
    // name:"你好",
    age:18 //继承而来的age属性也是必选的
}
console.log(obj);

//扩展
interface Info extends Omit<myInterface,'name'>{
    address:string
}
type Infos = Omit<myInterface,'name'> & {address:string}

/*  "类型别名type"和"接口interface"区别:
1. 接口的名字可以重复,同名的接口会合并;而类型别名的名字是唯一的
2. 接口仅用于描述对象类型,而类型别名可以用来表示基本类型、对象类型、联合类型、元组等
3. 接口可以使用 extends 关键字继承,而类型别名不能使用 extends 继承
*/

# 十五、泛型

在定义函数或类时, 如果遇到类型不明确时就可以使用泛型( 让函数或类更加灵活, 调用者来决定参数或者返回值类型 )

//numFun这个函数有个明显的缺陷就是写死了参数类型和返回值类型只能为number
function numFun(n1:number):string{
    return n1 + "1"
}

//anyFun这个函数也有缺陷,虽然调用者可以决定参数或者返回值类型,但是并不能保证参数和返回值类型相同
function anyFun(n1:any):any{
    return n1
}

//泛型可以解决以上的问题,定义一个泛型T,类似于一个未知数,它代表着某一种数据类型,只有在函数调用的时候才知道它的具体类型(即由调用者来决定)
function myFun<T>(num1:T):T{
    return num1
}

//调用时不指定泛型,TS会根据传入的参数进行推断
let n1 = myFun(18) //泛型T表示number 
let str = myFun("hello") //泛型T表示string
let flag = myFun(true)  //泛型T表示boolean
//调用时指定泛型(建议)
let n2 = myFun<number>(100) //泛型T表示number

//多个泛型
function myFun2<T,K>(n1:T,n2:K):T{
    return n1
}
let result = myFun2(18,"world") //泛型T表示number, 泛型K表示string
let result2 = myFun2<boolean,string>(true,"hello") //泛型T表示boolean, 泛型K表示string


//泛型实现接口
interface myInter{
    length:number
}
function myFun3<T extends myInter>(n1:T):number{
    return n1.length 
}
//因为泛型T继承myInter接口,所以泛型T中一定要有length属性,也就是调用函数时传入的参数必须要有length属性
let result3 = myFun3<string>("hello") //字符串'hello'有length属性 , 5
let result4 = myFun3({length:10})  //10
let result5 = myFun3({name:"孙悟空"}) //报错,传入的参数对象中没有泛型T所必须的length属性

//在TS的项目中,对http请求响应的res结果作声明类型时,可以借助泛型去定义一个全局的类型别名或者接口供整个项目使用
export type httpRes<T> = {
    code:number | string
    data:T
    message:string
    totalCount:number
}
//假设后台返回table表格的data为对象数组形式[{}],对象结构如下
interface resultRow{
    name:string | null 
    count:number
    createTime:string
    isValid:boolean
}
let res: httpRes<resultRow[]> = await getTableDataList()

//泛型类
class Cat<T>{
    name:T
    constructor(name:T){
        this.name = name    
    }
}
let cat = new Cat("奶猫")
let cat2 = new Cat<string>("奶猫2")



# 常用工具类型

一、Record
Record 是 TypeScript 中的一种特殊类型,它允许我们定义一个具有特定属性的对象类型。 Record 类型接收了两个泛型参数:第一个参数作为对象属性的类型,第二个参数作为对象属性值 的类型

// Record 源码如下( 此处看不懂源码没关系,下面关键字章节会详细讲述 )
type Record<K extends keyof any, T> = {
  [P in K]: T;
}
//比如我们要定义这么一个对象,对象的属性的类型都是字符串,属性值的类型都是数值;这时候可以用下面两种方法去声明这个对象的类型
var userInfo:Record<string,number> = {
    age:18,
    count:999,
    weight:65
}
type info = Record<string,number>
var userInfo:info = {
    age:18,
    count:999,
    weight:65
}
// 现在有了新需求,这个对象的属性只能是规定的某几个,不再是所有的string了;这时候可以用以下两种声明这个对象的类型
type userKey = 'age' | 'math' //联合类型的字面量
var newUserInfo:Record<userKey,number> = {
    age:18,
    math:91
}
var newUserInfo:Record<'age'|'math',number> = {
    age:18,
    math:91
}
//当对象的属性值是固定值时,可以这样去声明这个对象的类型
let newUserInfos:Record<string,{register:true}> 
newUserInfos = {
    name:{register:true},
    sex:{register:true}
}
//当然,属性值的类型不是固定的,也是可以配合联合类型使用的
const peopleInfo:Record<string,number | boolean>  = {
    age:18,
    isMale:true
}

二、Partial
Partial 接收一个泛型参数T,返回一个新类型,该新类型与 T 拥有相同的属性,但是所有属性皆为可选属性( 可以快速把某个接口类型中定义的所有属性变成可选的。 )

// Partial 源码如下
type Partial<T> = {
  [P in keyof T]?: T[P];
}
//现在有这么接口规定一个人的信息,都是必选的
interface info {
    name:string
    age:number
    tel:string
    register:boolean
}
const userInfo:info = {
    name:"张三",
    age:18,
    tel:'10086',
    register:true
}
//如果这个时候让这个人的所有信息变为非必选的,就可以借助 Partial来实现
const newSecondUserInfo:Partial<info> = {
    name:'张三'
}
//或者
type newInfo = Partial<info>
const newUserInfo:newInfo = { 
    name:"张三"
}
//上述的 类型别名newInfo 相当于
type newInfo = {
    name?: string | undefined;
    age?: number | undefined;
    tel?: string | undefined;
    register?: boolean | undefined;
}

三、Required
Required 字面意思为'必须的'。Required 是一个与 Partial 相反的工具类型; Required 接收一个泛型参数T,返回一个新类型,该新类型与 T 拥有相同的属性,但是所有属性皆为必选属性( 可以快速把某个接口类型中定义的所有属性变成必选的。 )

// Required 源码如下
type Required<T> = {
  [P in keyof T]-?: T[P];
}
interface info {
    name:string
    age:number
    tel?:string
    register?:boolean
}
const userInfo:info = {
    name:'张三',
    age:18
}
const newUserInfo:Required<info> = {
    name:"张三",
    age:18,
    tel:'10086',
    register:false
}
//或者
type newInfo = Required<info>
const newUserInfos:newInfo = {
    name:"张三",
    age:18,
    tel:'10086',
    register:false
}
//上述类型别名 newInfo 相当于
type newInfo = {
    name:string
    age:number
    tel:string
    register:boolean
}

四、Pick
pick单词有捡、挑选的意思。 当我们有一个已经存在的类型,并希望只使用该类型中的几个字段来创建一个新类型,这时就可以使用 Pick 来挑选你想要的字段( 选中的字段是否必选与传入的原类型一致 ), Pick 接收两个泛型参数,第一个泛型参数是对象类型T, 第二个参数是你要挑选的属性K,可以是单个字面量也可以是字面量联合类型

// Pick源码如下
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
}
type studentInfo = {
    name:string
    age?:number
    sex:string
    tel?:string
}
type newInfo = Pick<studentInfo,'name'| 'age'>
type newInfos = Pick<studentInfo,'tel'>
const newUserInfo:newInfo = {
    name:"张三"
    // age:18 挑选的age属性与 studentInfo 一致,是可选属性
}
//上述类型别名 newInfo 相当于
type newInfo = {
    name: string;
    age?: number | undefined;
}

五、Omit
omit字面意思为'忽略、省略'。 Omit是一个与Pick相反的工具类型;当我们有一个已经存在的类型,并希望忽略掉该类型中的几个字段来创建一个新类型,这时就可以使用 Omit 来忽略掉你想要去掉的字段( 剩余的字段是否必选与传入的原类型一致 ),返回一个新类型。 Omit 接收两个泛型参数,第一个泛型参数是对象类型T, 第二个参数是你要忽略的属性K,可以是单个字面量也可以是字面量联合类型

// Omit源码如下
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
interface studentInfo {
    name:string
    age?:number
    sex:string
    tel?:string
}
type userInfo = Omit<studentInfo,'name'> //忽略name
type userInfos = Omit<studentInfo,'name'|'sex'> //忽略 name 和 tel
const newUserInfo:userInfo = {
    sex:'1',
    age:18
}
const newUserInfos:userInfos = {
    tel:'10086'
}
//上述的 userInfo 和  userInfos相当于
type userInfo = {
    age?: number | undefined;
    sex: string;
    tel?: string | undefined;
}
type userInfos = {
    age?: number | undefined;
    tel?: string | undefined;
}
//扩展
interface Info extends Omit<studentInfo,'name'>{
    address:string
}
type Infos = Omit<studentInfo,'name'> & {address:string}

六、Exclude
Exclude 字面意思为'排除'。 主要用于从一个联合类型 中排除另一个类型, 返回一个新的类型。 从下面Exclude源码可以清晰的知道,Exclude的只要作用就是从联合类型T中去掉K,类似于Omit

// Exclude 源码如下
type Exclude<T,K> = T extends K ? never:T
type A = "name"|"age"|"tel"
type B = "name"
type C = "name"|"age"
type D = "name"|"addr"
type E = "name"|"age"|"tel"|"sex"
type newType = Exclude<A,B>  //  'age| tel'
type newTypes = Exclude<A,C> //  'tel'
type myType = Exclude<A,D> //   'age|tel'
type myTypes  = Exclude<A,E> //  never  


# 关键字和操作符

一、keyof和 in 操作符
keyof 操作符接受一个对象类型T作为参数,返回该对象类型属性名 key 组成的 字面量联合类型(注意是对象类型不是一个实际的对象,实际的对象想要将属性名key转化为字面量联合类型还需借助 typeof 操作符)

interface person {
    name:string
    age:number
    tel?:string
}
type newPerson = keyof person  //  'name'|'age'|'tel'
const p:newPerson = "name"

//特殊: keyof any
type newType = keyof any  //  string | number | symbol
//相当于
type newType = string | number | symbol

//约定泛型参数满足:当第一个泛型参数为对象时,第二个泛型参数必须是对象所拥有的的属性
function<T,K extends keyof T>(obj:T,key:K){
    return obj[key]
}

in 操作符右侧常会跟一个联合类型,通常会结合 keyof 一起使用,可用于遍历联合类型,类似于循环

type sex = "male" | 'female' | 'unknownSex'
type newType = {
    [P in sex]:number // P 是sex中的每一个遍历项
}
//相当于
type newType = {
    male: number;
    female: number;
    unknownSex: number;
}

二、extends关键字
2.1 接口继承
interface 继承可以是单个继承也可以多继承,用于表达类型组合。

interface baseParams{
    page:number
    rows:number
}
interface secondsParams {
    timestamp:string
}
//单个继承
interface params extends baseParams{ //  { page:number , rows:number ,name:string }
    name:string
}
//多继承( 用逗号隔开 )
interface newParams extends baseParams,secondsParams{} //{ page:number , rows:number ,timestamp:string }

interface newParams2 extends baseParams,secondsParams{ // { page:number , rows:number ,timestamp:string ,title:string}
    title:string 
}

2.2 泛型约束
extends用作泛型约束时,会限制类型参数必须满足某些条件,例如工具类型 Pick的源码。上面已经知道工具类型Pick接收两个泛型参数, 第一个泛型参数是对象类型T, 第二个参数是你要挑选的属性K,可以是单个字面量也可以是字面量联合类型

//Pick源码
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
}
/* keyof T 会将传入的原始对象类型T变为对象键名组成的字面量联合类型, 而 K extends keyof T 则表示想要挑选的属性K必须是
 字面量联合类型keyof T的子集;也就是 K 中的每个属性都必须是 原始对象类型T 中的属性名之一,其目的是保证你挑选的属性在T中是存在的,例 */

interface person{
    name:string
    age:number
    tel:string
}
type newPerson = Pick<person,'age'>
var p = {age:18}
type newPerson2 = Pick<person,'age'| 'name'>
var p1 = {
    name:"张三",
    age:18
}
type newPerson3 = Pick<person,"sex"> // 报错,类型“"sex"”不满足约束“keyof person”

三、typeof
typeof 操作符用于获取变量的类型,因此操作符后面接的始终是一个变量,这个变量可以是基本数据类型、引用数据类型或者是枚举等

let str:string = "hello"
console.log(typeof str); //string

let person = {
    name:"张三",
    age:18
}
type p = typeof person  //{ name:string , age:number }
//相当于
type p = {
    name: string
    age: number
}

let per = {
    sex:"女",
    otherInfo:{
        tel:'10086',
        age:18
    }
}
type newPer = typeof per
//相当于
type newPer = {
    sex: string
    otherInfo: {
        tel: string
        age: number
    }
}
//例:
const arr = [
    "hello",
    123
]
const arr1 = [
    {
        sex:"女",
        otherInfo:{
            tel:"10086",
            age:18
        }
    },
    {
        sex:"男",
        otherInfo:{
            tel:"10087",
            age:19
        }
    }
]

type newArr = typeof arr
type newArr1 = typeof arr1
//相当于
type newArr = (string | number)[]
type newArr1 = {
    sex: string;
    otherInfo: {
        tel: string;
        age: number;
    };
}[]

有些时候会看到 keyof typeof 跟一个枚举的这种写法,其目的是将每一个枚举值组合为联合字面量类型

enum SEX{
    male = "001",
    female = "002"
}
type sex = keyof typeof SEX
//相当于
type sex = "male" | "female"
const newSex:sex = "male"

/* keyof typeof 跟一个对象时,只能组合对象的第一层属性名,对深层无效( 这是keyof的限制决定的,typeof 可以正确
返回person的类型,但keyof 只感知person的类型的第一层级 )*/
let person = {
    name:"张三",
    age:18,
    otherInfo:{
        title:"标题"
    }
}
type a = keyof typeof person
//相当于
type a = "name" | "age"|'otherInfo'


# TypeScript 断言

# 类型断言

在某些时候,你比 TypeScript 更清楚某个变量的数据类型,比如你从异步请求中拿到一个值为 any 的值,但你清楚的知道这个值就是 string 类型的, 这个时候你可以通过类型断言的方式来告诉编译器,这个值就是 string 的,不应该再发出错误; 两种语法:尖括号< > 语法 和 as 语法

//尖括号<>语法
let str:any = "hello world"
let len:number = (<string>str).length

//as语法
len:number = (str as string).length
//有些DOM元素并没有src属性,想使用这种属性,此处需要告诉编译器document.getElementById("myImg")是HTMLImageElement类型的,是具有src属性的
let imgEle = document.getElementById("myImg") as HTMLImageElement
console.log(imgEle.src)

# 非空断言

当你明确的知道某个变量的值不可能为 null 或 undefined 时,你可以在变量后面加上 !(非空断言符号),来告诉编译器 ! 前面的值不可能是 null 或 undefined ,不应该再发出错误;

<div id="myBox"></div>
//告诉编译器id为myBox这个元素一定能获取到,不可能为null
let ele = document.getElementById("myBox")!


#

Class类可用于创建对象,可以理解为对象的模型;
修饰符
static : static 关键字声明静态属性(类属性)或静态方法(类方法), 此属性或方法只能使用类名访问, 不能通过实例对象访问; readonly : readonly 关键字用于声明一个只读属性或只读方法, 无法更改值;
static readonly : static readonly 用于声明一个静态只读属性或静态只读方法;

//类
class Person{
    MyEle:HTMLElement = document.getElementById("eleId")!; // !表示前面的结果不会为null
    name:string = "孙悟空"; //直接定义不加关键字的属性或方法是实例属性或实例方法,当前类、当前类的实例对象、子类都可以访问,Person类名无法访问
    static age:number = 18; //静态属性(类属性),此属性只能使用Person类访问,不能通过实例对象per访问 (如:Person.age)
    readonly sex:string = "男"; //只读属性,无法更改值  
    static readonly tel:string = "10086"; // 静态只读属性
    
    static say(){ //静态方法
        console.log("hello")    
    }
}
//类创建对象
let per = new Person()
console.log(per.name); //孙悟空
console.log(per.age); //per.age报错
console.log(Person.age); //18

Person.say() // hello

# 一、类的构造函数

类的构造函数会在类创建对象时调用一次,用于初始化对象 ;
注:构造函数中的 this 指向当前用类创建出来的实例对象 即 this ===> 实例对象

class Person{
   name:string;
   age:number;
   constructor(name:string,age:number){
        this.name = name
        this.age = age
   }
}
let per1 = new Person('孙悟空',18) //调用constructor()初始化对象
let per2 = new Person('小白龙',19)
console.log(per1);  // Person {name: '孙悟空', age: 18}
console.log(per2);  // Person {name: '小白龙', age: 19}

# 二、类的继承

可以将多个类共有的属性和方法写在一个父类中,这样只需要在父类中编写一次即可让所有子类同时拥有父类中的属性和方法;

  • 子类拥有父类的所有成员 ( 所有属性和方法 )
  • 子类中有与父类同名的方法时,会覆盖父类中的同名方法, 称为方法 重写
class Animal{
   animalName:string;
   animalAge:number;
   constructor(name:string,age:number){
        this.animalName = name
        this.animalAge = age
   }
   sayHello(){
       console.log("this is an animal")   
   }
}

//🐱Cat类 继承Animal类
class Cat extends Animal{ //此时,Animal类称为父类,Cat类为子类
     run(){ //子类Cat中添加自己的属性和方法
       console.log(`${this.animalName}在跑`); 
     }
     sayHello(){ //重写,会覆盖父类中的sayHelleo()
         console.log("this is a cat")     
     }
}

//🐕Dog类 继承Animal类
class Dog extends Animal{
    
}

let cat = new Cat("奶牛猫",3)
cat.run() // 奶牛猫在跑
cat.sayHello()// this is a cat

# 三、super关键字

2.1  在类的方法中,super 表示当前类的父类 即 super ==> 父类;

class Animal{
  name:string;
   constructor(name:string){
        this.name = name
   }
   sayHello(){
       console.log("this is an animal")   
   }
}

//🐱Cat类 继承Animal 类
class Cat extends Animal{
   catSay(){
     super.sayHello(); //通过super关键字调用了父类的sayHello()方法
   }
}
let cat = new Cat("奶牛猫")
cat.catSay() //this is an animal

2.2 在子类中,如果子类也有构造函数 constructor( ), 此时发生重写,覆盖了父类中的构造函数 constructor( ), 这时候必须在子类的构造函数 constructor( ) 中首先使用 super( ) 来调用父类的构造函数, 并且必须传入父类的 constructor( ) 所需要的值。 语法硬性要求, 必须调用super( ), 并传入父类需要的参数;例:

class Animal{
  animalColor:string;
   constructor(color:string){
        this.animalColor = color
   }
   sayHello(){
       console.log("this is an animal")   
   }
}

//🐱Cat类 继承Animal 类
class Cat extends Animal{
    age:number
   constructor(color:string,age:number){
       super(color);//子类中的constructor()中必须首先调用super(),并传入父类的构造函数constructor()所需要的color值,
       //也就是说子类的构造函数也需要接收父类的构造函数所需要的参数,并在子类创建对象的时候传递进来,再经super()传入;
       this.age = age ;  
   }
}
let cat = new Cat("白色",3)
console.log(cat) //Cat {animalColor: '白色', age: 3}

# 四、抽象类

abstract 关键字用于声明一个抽象类或抽象方法,抽象类就是专门用来被其他类继承的,不能用于创建对象; 抽象方法只能定义在抽象类中,并且抽象方法没有方法体( 只提供方法,具体实现由子类决定 ),因此抽象类的子类必须对抽象方法进行重写,例:

//抽象类
abstract class Animal{
  name:string;
   constructor(name:string){
        this.name = name
   }
   abstract sayHello():void
}
let monkey = new Animal("孙悟空") //报错,抽象类不能创建对象

class Cat extends Animal{
    sayHello(){ //必须重写父类抽象方法
        console.log("cat")    
    }
}

class Dog extends Animal{ 
    //报错,未重写父类的抽象方法
}

# 五、类实现接口

接口用来定义一个类的结构, 规定一个类中有哪些属性和方法, 当然也可以当做类型声明来使用; 接口中的属性不能有初始值, 方法也不能有方法体, 只用于规定类的结构;

interface myInterface{
    name:string;
    age?:number;
    color:string;
    sayHello():void
}
//类实现接口 ( 类实现接口:就是让类的结构满足接口的要求 )
class Animal implements myInterface{
    name:string;
    age:number;
    color:string;
    constructor(name:string,age:number,color:string){
        this.name = name;
        this.age = age;
        this.color = color;    
    }
    sayHello(){
        console.log("Animal类实现了myInterface接口")    
    }
}

# 六、属性的封装、存取器

public : public修饰的属性可以在任意位置 (内部、外部、子类等)访问和修改,默认值(类中没有修饰符的属性和方法都是public的)
private : 私有属性,private 修饰的属性只能在 当前类的内部 进行访问和修改,当前类的子类和当前类创建的实例对象是无法访问到的;(注:可以通过属性存取器返回私有属性,在外部间接获取到私有属性);
protected : 受保护的属性, protected 修饰的属性只能在 当前类和当前类的子类中 访问和修改,当前类创建的实例对象是无法访问到的;

class Cat{
  name:string; //public属性
  private age:number;
  protected color:string //私有属性
   constructor(name:string,age:number,color:string){
       this.name = name
       this.age = age
       this.color = color
   }
   //属性存取器getter、setter
   get getAge(){ //通过存取器getter可以让外部间接访问到私有属性age
       return this.age    
   }
   set setAge(val:number){  //通过存取器setter可以设置私有属性的值
        if(val >= 0){
           this.age = val       
       }      
   }
   getCatAge(){  //外部实例对象调用此方法,也可以实现外部访问私有属性
       return this.age   
   }
   setCatAge(val:number){
       if(val >= 0){
           this.age = val       
       }   
   }
}

let cat = new Cat("小白兔",3,"白色")
//存取器读取属性:
let catAge = cat.getAge // 访问存取器间接访问私有属性age  3
//存取器设置属性:
cat.setAge = 18  
console.log(cat.getAge) // 18
//调用方法
let catAge1 = cat.getCatAge() 
console.log(catAge1) // 18

//访问范围
class MiniCat extends Cat{
    test(){
        return this.name // 可以   
    }
    test1(){
        return this.age //报错,子类无法访问到父类的私有属性    
    }
    test2(){
        return this.color // 可以   
    }
}

# 七、类的简写

在构造函数的形参前添加上属性修饰符可简化类的写法;

class Cat1{
    public name:string;
    protected age:number
    constructor(name:string, age:number){
        this.name = name
        this.age = age
    }
}
//等同于上面
class Cat{
     constructor(public name:string,protected age:number){
     }
}
 let cat = new Cat("白兔",3)
 console.log(cat);