接口与类
TS 中的类
类的组成,定义类通过使用关键字 ‘class’ 进行定义的,后面紧跟的是类的名称,类它包含了以下的几个模块(类的成员)
- 字段 - 字段是类里面声明的变量,字段表示对象的字段有关的数据
- 构造函数 - 类实例化时被调用,可以为类的对象分配内存
- 方法 - 方法为对象要执行的操作
class User {
1. 字段
str: string
2. 构造函数
constructor(str:string) {
this.u = str
}
3. 方法
sum():void {
return
}
}
类属性和方法的修饰符
在 TS 中,可以使用修饰符对类中的属性和方法进行保护,和构造器的访问,在 TS 中有三个修饰符,修饰类中的属性、方法、构造器
- public默认,他代表的是任何创建实例的人都可以一访问到类中的所有内容,类中不书写就是默认使用的是 public,公开的
- protected受保护的,他代表的是只有创建他的类内部或者是继承了他的子类,才可以使用这个受保护的属性/方法
- private私有的,只能在定义私有属性的类中进行使用
公开的属性
class Car {
1. 字段
public engine:string; // 默认写法,属性以及方法都是公开的外面都可以访问到
2. 构造函数
constructor(engine:string) {
this.engine = engine
}
3. 方法
public disp():void {
console.log("发动机为 : "+this.engine)
}
}
受保护的
所谓的受保护的属性和方法,就是只允许自身定义的类中进行调用,再有就是他有子类,子类继承了父类,子类可以访问父类定义的受保护的属性
class Car {
1. 字段
protected engine:string; // 默认写法,属性以及方法都是公开的外面都可以访问到
2. 构造函数
constructor(engine:string) {
this.engine = engine
}
3. 方法
public disp():void {
console.log("发动机为 : "+this.engine)
}
}
let hd = new Car('小明')
class Str extends Car {
public getEngine():void {
console.log(this.engine);
}
}
new Str('小红').getEngine()
尝试着在实例类之后,通过实例对象去访问受保护的属性,发现并不能进行访问
私有的
类定义的私有属性和方法,只能通过创建者自身进行调用,通过继承的子类也是不可以访问到的
class Car {
1. 私有字段
private engine:string; 01 默认写法,属性以及方法都是公开的外面都可以访问到
2. 构造函数
constructor(engine:string) {
this.engine = engine
}
3. 私有方法
private disp():void {
console.log("发动机为 : "+this.engine)
}
}
let hd = new Car('小明')
子类继承,修改父类中的属性 & 方法(重写)
使用了修饰符号之后,继承的子类并不是可以随便的修改父类中的属性或者是方法,比如【受保护的 & 私有的】他们比较特殊
- 私有的,子类继承父类,但是子类是不可以对父类中任何私有的属性或者是方法进行修改的,并且子类还不能调用父类的方法,如果说在重新定义一个同名的方***不会进行覆盖,答案是错误的,仅切仅有受保护和公开的才可以对方法进行覆盖操作
class Prose {
private info():void {
console.log('父类中的方法');
}
}
class User extends Prose{
name: string;
constructor(name:string) {
super();
this.name = name;
}
public info():void {
console.log(this.name);
}
}
let user = new User('子类')
console.log(user.info()); // 子类
如果使用【private : 私有】再次对父类中的info方法进行覆盖是不可以的
!!! 注意:受保护的和私有的修饰,子类是可以覆盖的,但是覆盖父类的方法还是有要求的,比如说父类中的方法是受保护的,但是我们在子类进行覆盖的时候,子类的设置成了私有的方法,这时候是不可以进行覆盖的,会打印错误,但是如果是同级或者是公共的,覆盖是完全没问题的
总结: 子类要想覆盖父类的方法,必须做到要与父类同级或者是比父级修饰符更低级的修饰符,才可以进行覆盖
class User {
public sum():void {
console.log(1111);
}
}
class Str extends User {
public sum():string {
return '你好'
}
}
console.log(new Str().sum());
- 重写的概念 类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写。
其中 super 关键字是对父类的直接引用,该关键字可以引用父类的属性和方法。
class PrinterClass {
doPrint():void {
console.log("父类的 doPrint() 方法。")
}
}
class StringPrinter extends PrinterClass {
doPrint():void {
super.doPrint() 1. 调用父类的函数
console.log("子类的 doPrint()方法。")
}
}
readonly的妙用
他是一个 只读的 一个修饰符,允许你可以访问属性,但是你不能对他进行修改,当然这句话也不是完全正确的,当构造函数初始化的时候,readonly是可以被修改的
class Axios {
1. 设置只读属性
readonly stic: string = 'www.baidu.com'
public get():void {
console.log( `你的请求地址是${this.stic}` );
}
}
let axios = new Axios();
console.log(axios.get());
1. 构造函数初始化的时候可以被修改
class Axios {
readonly stic: string = 'www.baidu.com'
public constructor(stic?: string) {
this.stic = stic || this.stic
}
public get():void {
console.log( `你的请求地址是${this.stic}` );
}
}
let axios = new Axios('www.lagou.com');
console.log(axios.get());
TS 中的构造函数
在 TS 中类的构造函数创建的方式是与JavaScript(原生),有一定的差别,在初始化数据的时候,写法有一些变化
- 第一种,你需要在构造函数的外面,先要对属性进行类型的定义,才可以在构造器中访问到
class User {
public n:string
public constructor(n:string) {
this.n = n
}
public info():void {
console.log(this.n);
}
}
let hd = new User('你好');
hd.info()
- 第二种,简便的写法,结合构造函数,一起定义
class User {
public constructor(public n:string) {
this.n = n
}
public info():void {
console.log(this.n);
}
}
let hd = new User('你好');
hd.info()
静态属性与方法
静态属性与静态方法,在类中通过 static 关键字进行定义,只能通过类自身进行调用,通过 new 关键字实例化的对象是不可以访问到静态属性与静态方法的
class User {
static str:string = 'houdunren'
static info():string {
return User.str
}
}
let user = new User()
console.log(User.info());
单例模式
什么是单例模式,所谓的单例模式就是,创建一个类,通过 new 关键字进行实例化,只能实例化一次,多次实例化也只是生产一个对象出来
class Axios {
private static instance: Axios | null = null
constructor() {}
static info(): Axios {
if(Axios.instance == null) {
Axios.instance = new Axios();
}
return Axios.instance
}
}
let axios = Axios.info()
抽象类
- 抽象类,他是一种规范模板,所有的子类,要想继承 抽象类,他就必须要实现抽象类里面定义的所有内容
- 定义抽象类,因为他是一个规范,所以抽象类里面只需要负责定义,它不需要具体的实现
- 想要实现抽象类里的内容,就要定义子类继承 抽象类,去实现抽象类中的内容
- 抽象类,他不可以被实例化,不可以被 new 关键字实例化出对象
- 要想使用抽象方法或者是抽象属性,就必须存在于抽象类当中,并且抽象类也是类他除了不可以通过 new 关键字进行实例化,它自身可以定义方法和属性的。
abstract class AnimationA {
abstract move(): void 抽象方法
abstract str: string 抽象属性
protected getPos():number[] {
return [100,100]
}
}
class You extends AnimationA {
public str:string = 'you'
public move(): void {
console.log('你好')
}
}
class Me extends AnimationA {
public str:string = 'me'
public move(): void {
console.log('我好');
}
}
接口
- 接口是定义一个规范,接口的内部没有具体的实现,只有规范,它内部不允许出现抽象方法,不允许出现实现方法
- 接口通过 interface 去创建一个接口,并且去创建约束条件,并且定义接口名字必须大写,首字母
- 使用关键字 implements 去约束某一个类的行为,被约束的类必须实现接口中的规范
interface Adget {
moeo(): void
}
1. 约束没有继承的类
class Str implements Adget {
moeo(): void {
console.log('哈哈哈');
}
}
2. 约束有继承的类
abstract class Animation {
protected getPos():number[] {
return [100,100]
}
}
class You extends Animation implements Adget {
public str:string = 'you'
public move(): void {
console.log('你好')
}
}
对象的约束
interface User {
name: string
age?:number 1. 可选参数
info(): void
[key: string]: any 2. 可以向对象里面添加属性,键的类型必须是字符串,值是任意类型
}
let obj:User = {
name: 'John',
age: 34,
info(): void {
console.log(this.name + this.age)
},
sex: 'male',
str():void {
console.log(this.sex)
}
}
obj.str()
obj.info()
!! 所需要注意的是,在定义接口规范的时候,一个对象自身是可以向对象内部追加属性和方法的,所以在定义接口规范的时候,就需要注意最后一个选项了,这样定义规范的目的是,允许对象,给自身添加属性或者是方法
[key: string] : any
接口之间的继承
接口与接口之间是可以尽心继承的,类实现了User接口的同时,还要去实现Adget的接口规范,通过使用 extends 关键字,实现两个接口之间的继承
interface Adget {
moeo(): void
}
interface User extends Adget {
name: string
age?:number
info(): void
[key: string]: any
}
如果不适用关键字 extends 去接口与接口之间的继承,我们也可以使用 ‘逗号’ ,实现一个类实现多个接口的规范,也是可以的
interface Adget {
moeo(): void
}
interface User {
name: string
age?:number
info(): void
}
class Str implements Adget,User {
public name: string = 'You';
public age: number = 18
public info(): void {
console.log(this.name + this.age);
}
public moeo():void {
console.log(this.name);
}
}
let str = new Str();
str.info()
接口约束函数参数
interface User{
name: string;
age: number;
}
let obj:User = { 1. 将接口作为对象的类型,来约束对象
name: '小明',
age: 20
}
function sum(option: User):User {
option.name = '小红'
return option
}
console.log(sum(obj));
接口约束类
interface Us {
name: string;
age: number
}
class Str {
_init: Us; 1. 约束类在传递数据时候,必须传递对象并且要实现接口的规范
public constructor(n: Us) {
this._init = n
}
public init():Us {
console.log(this._init);
return this._init
}
}
let str = new Str({name: '小明', age:18})
str.init()
接口约束数组
interface User {
name: string;
age: number;
}
let arr: User[] = [{ 1. 将接口User定义给数组当做类型,并且内部的内容,只能是接口中的内容
name: '小明',
age: 10
}]
console.log(arr);
接口声明函数与接口合并
1. 声明一个接口,内部实现的是箭头函数
{
interface User {
(n: number): void;
}
const _init: User = (n: number) => {
return n
}
_init(5)
}
2. 声明接口,内部实现函数声明,普通函数
{
interface User {
(n:number): number;
}
let _init: User
_init = function (n: number): number {
return n
}
_init(5)
}
简单的介绍一下接口声明合并的问题,我们都知道 JavaScript 里面声明同名的属性后者会覆盖前者,并且将前者的内容更改掉,但是在 TS 里面接口的合并,并不是覆盖的关系,而是合并的关系,假如有一天,你有一个接口内部规范不足以支撑现在的程序,你可以创建一个同名的接口规范,同名的规范之间会进行合并,然后去约束【数组 对象 函数】 等等
type介绍
- type 的功能与接口 interface 他们很相似,都是定义规范,然后使用规范去约束对象数组等等。
1. 使用type定义
type User {
name: string;
age: number,
init(): string, 4. 函数类型
[key: string]: any 3. 索引类型
}
2. 使用方式
const obj:User = {
name: '小明',
age: 18,
init():string { 2. 函数类型
return this.name
},
sex: '男' 1. 索引类型,所谓的索引类型,就是可以相对想内可以添加属性,前提是键是‘字符串’
}
- type 与 interface 之间的区别,他们之间的最大区别就是 声明 合并的问题,type 与 interface声明合并完全是两个概念,type要想声明合并,他就需要使用 ‘&’ 符号合并,然后通过 type 定义一个新的规范,将合并后的规范赋值给新的规范,用新的规范约束 类、对象、数组等等。
- 合并之后,新的 type 声明 是一个联合类型,只要里面其中一个规范实现了,也是没问题的
type User {
name: string;
}
type Str {
age: number;
}
type U = User & Str
- 可以使用 implements 关键字,将 type 声明约束类
type User {
name: string;
}
class Str implements User {
name:string = '小明'
}
- type给基本类型起别名操作
type IsAdmin = boolean
type User {
bool: IsAdmin // 使用的时候,需要使用别名即可
}
const obj: User = {
bool: true
}
- type 的联合类型声明
type sex = 'boy' | 'girl'
type User {
sex: sex
}
const obj:User = {
sex: 'boy' || 'girl'
}