接口的作用:
在面向对象编程中,接口除了可以复用以外,接口还是一种规范的定义,他定义了行为和动作的规范,起到了一种限制和规范的作用。接口不关心类内部的状态。
我们通过关键字:interface来定义一个接口
// 定义接口
interface ObjType {
name: string,
age: number,
price: string,
}
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号。
// 定义接口
interface ObjType {
name: string,
age: number,
price: string,
color?: string,
}
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly
来指定只读属性
// 定义接口
interface Point {
readonly x:number;
readonly y:number;
}
let p1:Point = {x:10,y:20}
p1.x = 10;
在接口中也可以定义一个函数,为了使用接口表示函数类型,我们需要给接口定义一个调用签名,它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
// 函数接口
interface Show {
(name:string,age:number):any
}
// 定义函数
const myShow:Show = function(name:string,age:number){
console.log(` 我叫${name},今年${age}岁了 `)
}
// 调用方法
myShow("xlr",22)
接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。也就是说一个对象可以同时做为函数和对象等使用
// 混合类型
interface Counter {
// 定义函数类型
(start: number): string;
interval: number;
// 定义函数返回值类型
reset(): void;
}
和类一样,接口也可以相互继承,一个接口可以继承多个接口,创建出多个接口的合成接口
interface Shape {
color: string;
}
// 接口Square继承了接口Shape,那么接口Square中含有的属性就是:color和sideLength这种属性
interface Square extends Shape {
sideLength: number;
}
// 强制转换类型
let square = {};
square.color = "blue";
square.sideLength = 10;
接口也能明确的强制一个类去符合某种契约。ps:我们可以先认识一下类的概念
通过对类的学习,我们继续来看接口和类的关系。
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。(通过接口去规范一个类的类型)
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}
类(Class)定义了一件事物的抽象特点,包含它的属性和方法
1、定义类
在 TypeScript
中,我们也是通过 Class
关键字来定义一个类, 使用 constructor
定义构造函数。
构造函数: constructor
- 主要用于初始化类的成员变量属性
- 类的对象创建时自动调用执行
- 没有返回值
// 类的定义
class Animal {
// 属性
name: string;
// 构造函数
constructor(name: string) {
this.name = name;
}
// 方法
sayHi(): string {
return `My name is ${this.name}`;
}
}
2、类的继承
在TypeScript里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。(也就是子类中可以使用父类中的所有属性和方法。ps:父类中的属性和方法为默认public)
class Dog extends Animal {
constructor(name: string) {
super(name)
}
}
const dog = new Dog('旺财')
console.log(dog.sayHi())
3、修饰符(定义属性的时候,我们可以对属性进行修饰)
修饰符有:public(公共的)、private(私有的)、protected(受保护的)、readonly(只读属性)
我们可以简单的理解下这些修饰符:
- public 任意位置都可以访问
- private 只能在本类中访问
- protected 修饰的属性或方法,可以在子类中访问,但是通过实例是不能访问的
- 构造函数也可以被标记成 protected。这意味着这个类不能在包含它的类外被实例化,但是能被继承。
- readonly只读属性,必须初始化,只读属性必须在声明时或构造函数里被初始化
class cat extends Animal1 {
// 只读属性,必须初始化,只读属性必须在声明时或构造函数里被初始化
public readonly color: string = 'yellow'
constructor(name, type, age, color) {
super(name, type, age);
this.color = color;
}
show() {
console.log(` name=${this.name},type=${this.type},age=${this.age} `)
}
}
4、存储器(当我们将属性定义为私有属性的时候,我们在类的外部如何获取和修改私有属性)
TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
// 使用参数属性进行修改:将声明和赋值一起来完成
class Animal {
// 构造函数不能是私有的 private
// 原因:变成私有的只能在类的内部使用,我们在new实例的时候我们调用构造函数,
// 如果构造函数使用私有的,new实例的时候会报错
constructor(private _name: string) {
};
// 外部使用私有属性的时候,对外提供一个公共的方法,获取
get name(): string {
return this._name
};
// 如果只有 get 没有 set 默认是readonly
set name(val: string) {
// 对修改数据时,设置限制
// 对输入数据的合法性进行判断
if (!val || val.replace(/(^\s*|\s*$)/g, '') === '') {
this._name = val;
}
}
}
5、静态属性(当每个实例都要使用这个属性的时候,我们就可以将这个属性变成静态属性)
类上有静态属性和实例属性
①实例属性:那些仅当类被实例化的时候才会被初始化的属性。在访问时,通过 实例名.属性名或方法名[this.属性名或方法名]
②静态属性:归类所有,不属于具体的实例,类名.属性名或方法名。在声明时,通过static关键字来表示静态属性
class Point {
// 静态属性归类所有,当一个属性公共使用的时候,可以使用静态属性
// 静态属性可以赋值
static origin: PointIner = { x: 0, y: 0 }
// 构造器是静态属性
constructor(private _pointObj:PointIner){}
// 展示点 pointObj到原点的距离
distance(){
const x = this._pointObj.x - Point.origin.x
const y = this._pointObj.y - Point.origin.y
// Math.sqrt 开方 Math.pow 多少次方
return Math.sqrt(Math.pow(x,2)+Math.pow(y,2))
}
}
6、把 类当做接口使用(每个属性或对象等都有类型,那么类也是有类型)
我们创建一个对象的时候,这个对象的类型就类的类型。那么我们这个类也是有类型的。这就是我们所说的类类型。那么既然类也是有类型的,我们就可以把类当成一个接口去使用。
// dog的类型就是Dog类型
const dog:Dog = new Dog();
// 那么我们这个类也是有类型的
let otherAnimal:typeof Dog = Dog
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
7、抽象类(抽象类只能继承不能new)
注意:如果子类继承了抽象方法,那么子类一定要实现抽象方法。
当子类去继承父类的方法的时候,当父类提供的方法不满足子类的需求的时候,子类可以在类的内部重写这个方法(override)。在调用这个方法的时候,先调用子类重写的方法。如果父类提供的方法子类一直重写,那么父类就没有必要去创建这个方法了。我们就可以只在父类中声明这个方法就可以了。这就是抽象方法。抽象方法必须在抽象类中。
(1)子类重写override
class Animal2 {
constructor(private voice:string){}
// 没有实现的方法叫做抽象方法
sing(){
console.log("----wenwen-----")
}
}
class Dog extends Animal2 {
// 方法的重写 override
sing() {
console.log('---wangwang---')
}
}
(2)定义抽象类:使用关键字abstract
// 抽象类 --- 抽象方法 抽象类中可以有没有实现的方法:抽象方法
// 抽象类中也可以有普通方法,抽象类不能创建实例
abstract class Animal2 {
constructor(private voice:string){}
// 没有实现的方法叫做抽象方法
abstract sing();
}
(3)子类实现抽象方法
// 子类中必须实现抽象方法
class Cat extends Animal2 {
sing() {
throw new Error("Method not implemented.");
}
}
1、接口之间可以相互继承,从而实现接口的复用;类之间也可以相互继承,从而实现方法和属性的复用
2、接口可以通过类来实现,但是接口只能约束类的公有成员
3、接口可以抽离类的成员,抽离的时候包括共有成员,私有成员,受保护成员
1、不同类之间公有的属性或方法,可以抽象成一个接口(interfaces), 而抽象类是供其他类继承的基类
2、抽象类本质是一个无法被实例化的类,可以包含成员的实现细节,而接口仅能够用于描述,既不能提供方法的实现细节,也不为属性进行初始化
3、一个类可以继承一个类或抽象类,但可以实现(implements)多个接口
4、抽象类也可以实现接口