我们知道, JS 是靠原型和原型链来实现面向对象编程的,es6 新增了语法糖 class。
TS 通过 public
、private
、protected
三个修饰符来增强了 JS 中的类。
在 TS 中,写法和 JS 差不多,只是要定义一些类型而已。
定义一个Person类,有属性name和方法speak
class Person { ? name: string ? constructor(name: string) { ? ? ? this.name = name ? } ? speak() { ? ? ? console.log(`${this.name} is speaking`) ? } } const p1 = new Person('lin') ? ? // 新建实例 ? p1.name ? ? ? ? ? ? ? ? ? ? ? ? ? // 访问属性和方法 p1.speak()
使用extends关键字实现继承,定义一个Student类继承自Person类。
总结: 类和类之间如果要有继承关系,需要使用extends关键字
子类中可以调用父类中的构造函数,使用的是super关键字(包括调用父类中的实例方法,也可以使用super)
子类中可以重写父类的方法
class Student extends Person { ? study() { ? ? ? console.log(`${this.name} needs study`) ? } } const s1 = new Student('lin') s1.study()
继承之后,Student 类上的实例可以访问 Person 类上的属性和方法。speak和study方法和name方法。
上例中 Student 类没有定义自己的属性,可以不写 super ,但是如果 Student 类有自己的属性,就要用到 super 关键字来把父类的属性继承过来。
比如,Student 类新增一个 grade(成绩) 属性,就要这么写:
class Student extends Person { ? grade: number ? constructor(name: string,grade:number) { ? ? ? super(name) ? ? ? this.grade = grade ? } } const s1 = new Student('lin', 100)
子类对父类的方法进行了重写,子类和父类调同一个方法时会不一样。
父类型的引用指向了子类型的对象,不同类型的对象针对相同的方法,产生了不同的行为。
class Student extends Person { ? speak() { ? ? ? return `Student ${super.speak()}` ? } }
TS 中一般对抽象方法实现多态,详细见后文抽象类。
public
,公有的,一个类里默认所有的方法和属性都是 public。
public 可写可不写,不写默认也是 public。
public修饰符,类中成员默认的修饰符,代表的是公共的,任何位置都可以访问类中的成员
class Person { ? public name: string ? public constructor(name: string) { ? ? ? this.name = name ? } ? public speak() { ? ? ? console.log(`${this.name} is speaking`) ? } }
private
,私有的,只属于这个类自己,它的实例和继承它的子类都访问不到。
将 Person 类的 name 属性改为 private。
类中的成员如果使用private来修饰,你们外部是无法访问这个成员数据的。
class Person { ? private name: string ? public constructor(name: string) { ? ? ? this.name = name ? } ? public speak() { ? ? ? console.log(`${this.name} is speaking`) ? } }
protected
受保护的,继承它的子类可以访问,实例不能访问。
将 Person 类的 name 属性改为 protected。
class Person { ? protected name: string ? public constructor(name: string) { ? ? ? this.name = name ? } ? public speak() { ? ? ? console.log(`${this.name} is speaking`) ? } }
首先是一个关键字,对类中的属性成员进行修饰,修饰符后,该属性成员,就不能在外部被随意的修改了。
类的成员方法可以直接返回一个 this
,这样就可以很方便地实现链式调用。
class StudyStep { step1() { ? console.log('listen') ? return this } step2() { ? console.log('write') ? return this } } const s = new StudyStep() s.step1().step2() ? // 链式调用
class StudyStep { step1() { ? console.log('listen') ? return this } step2() { ? console.log('write') ? return this } } class MyStudyStep extends StudyStep { next() { ? console.log('before done, study next!') ? return this ? } } const m = new MyStudyStep() m.step1().next().step2().next() // 父类型和子类型上的方法都可随意调用
这样就保持了父类和子类之间接口调用的连贯性。
interface 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。
interface 同样可以用来约束 class,要实现约束,需要用到 implements
关键字。
implements 是实现的意思,class 实现 interface。
比如手机有播放音乐的功能,可以这么写:
interface MusicInterface { ? playMusic(): void } class Cellphone implements MusicInterface { ? playMusic() {} }
不同的类有一些共同的属性和方法,使用继承很难完成。
比如汽车(Car 类)也有播放音乐的功能,你可以这么做:
用 Car 类继承 Cellphone 类
找一个 Car 类和 Cellphone 类的父类,父类有播放音乐的方法,他们俩继承这个父类
很显然这两种方法都不合常理。
实际上,使用 implements,问题就会迎刃而解。
interface MusicInterface { ? playMusic(): void } class Car implements MusicInterface { ? playMusic() {} } class Cellphone implements MusicInterface { ? playMusic() {} }
再比如,手机还有打电话的功能,就可以这么做,Cellphone 类 implements 两个 interface。
interface MusicInterface { ? playMusic(): void } interface CallInterface { ? makePhoneCall(): void } class Cellphone implements MusicInterface, CallInterface { ? playMusic() {} ? makePhoneCall() {} }
使用 implements 只能约束类实例上的属性和方法,要约束构造函数和静态属性,需要这么写。
interface CircleStatic { ? new (radius: number): void ? pi: number } const Circle:CircleStatic = class Circle { ? static pi: 3.14 ? public radius: number ? public constructor(radius: number) { ? ? ? this.radius = radius ? } }
TypeScript支持通过getters和setters来截取对象成员的访问,它能帮助你有效的控制对对象成员的访问。
class Person{ firstName string= 'A' lastName string= 'B' get fullName (){ return this.firstName + '-' +this.lastName } set fullName (value){ const names = value.split('-') this.firstName = names[0] this.lastName = names[1] } } ? const P = new Person() console.log(p.fullName) ? p.firstName ='C' p.lastName ='D' console.log(p.fullName) ? p.fullName = 'E-F' console.log(p.firstName,p.lastName) ?
abstract抽象类作为其他派生类的基类使用,不能被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract 关键字是用于定义抽象类和抽象内部定义抽象方法。
/* 抽象类: 不能创建实例对象,只有实现类才能创建实例对象 可以包含未实现的抽象方法 / abstract class Animal { ? ?abstract cry () ? ?run(){ ? console.log('run()') ? } } class Dog extents Animal{ cry(){ console('Dog cry()') } } const dog = new Dog() dog.cry() dog.run()
类的类型,类的类型可以通过接口来实现。
//定义一个接口 interface ifly { 该方法没有任何的实现 fly() } 定义一个类,这个类的类型就是上面定义的接口(实际上也可以理解为,ifly接口约束了当前的这个Person) class person implements ifly{ 实现接口中的方法 fly(){ console.log('我会飞,我是超人') } } 实例化对象 const person = new person() person.fly() ? 定义一个接口 interface iswim{ swim() } ? 定义一个类,这个类的类型是ifly和iswim(当前这个类可以实现多个接口,一个类同时也可以被多个接口进行约束) class person2 implements ifly,iswim{ fly(){ console.log('我会飞,我是超人') }, swim(){ console.log('我会游泳,我是蛙人') } } 实例化对象 const person2 = new person2() person2.fly() person2.swim() 总结:类可以通过接口的方式,来定义当前这个类的类型 类可以实现一个接口,类可以实现多个接口,要注意,接口中的内容都要真正的实现。 ? 定义了一个接口,继承其他的多个接口 interface person3 implements imyflyandswim{ fly(){ console.log('我会飞3,我是超人') }, swim(){ console.log('我会游泳3,我是蛙人') } } const person3 = new person3() person3.fly() person,swim() 总结:接口和接口之间叫继承(使用的是extends关键字),类和接口之间叫做实现(使用的是implements)。