typescript的类与接口 类与接口

我们知道TypeScript能够提前用上最新ES版本的一些特性。例如,类的一些用法在ES7和Ts中就得到了更新

实例属性

在ES6中,我们只能在constructor中来定义实例属性

例如:

1
2
3
4
5
6
class  {
constructor(name,age) {
this.name = name;
this.age = age;
}
}

然而,ts中允许我们直接在类中定义实例属性

1
2
3
4
5
6
7
8
9
10
11
class Person {
name: string = 'lee';
age: number = 18;
constructor() {

}
}

let lee = new Person();
console.log(lee);

静态成员

我们也可以在类上定义静态成员,这些静态成员只存在于类本身而不会被实例所得到的

我们使用static关键字来定义静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {
name: string;
age: number;
static id: string = '12345';
constructor(name: string,age: number) {
this.name = name;
this.age = age;
},
static getId() {
return this.id;
}
}

let ming = new Person('ming',18);
console.log(ming);
// Person { name: 'ming', age: 18 }

// 我们可以看到在实例上并不能访问到静态成员

console.log(Person.id);
// '12345'

console.log(Person.getId());
// '12345'

类的继承

类的继承在面向对象的特性中是一个重要的特点

看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Person {
name: string;
age: number;
static mes: string = 'short mes';
constructor(name: string,age: number) {
this.name = name;
this.age = age;
}
static getMes(): any {
return this.mes;
}
sayHello(): void {
console.log('Person say hello');
}
}

class Man extends Person {
sex: string = 'man';
friends: Array<string>;
constructor(name: string,age: number,friends: Array<string>) {
super(name,age);
this.friends = friends;
}
sayHello(): void {
console.log('Man say hello');
}
}

class Woman extends Person {
sex: string = 'woman';
friends: Array<string>;
constructor(name: string,age: number,friends: Array<string>) {
super(name,age);
this.friends = friends;
}
sayHello(): void {
console.log('woman say hello');
}
}

let ming = new Man('ming',18,['lee','alice']);

let alice = new Woman('alice',17,['lee,ming']);

console.log(ming,alice);
/*
Man { name: 'ming', age: 18, sex: 'man', friends: [ 'lee', 'alice' ] }

Woman { name: 'alice', age: 17, sex: 'woman', friends: [ 'lee,ming' ] }
*/

ming.sayHello();
// 'Man say hello'

alice.sayHello();
// 'woman say hello'

console.log(Man.getMes());
// 'short mes'

console.log(Woman.getMes());
// 'short mes'

通过以上的例子,我们基本可以总结出关于类的继承方面的几点:

  1. 使用extends关键字进行类的继承
  2. 在子类的构造函数中调用super来调用父类的构造函数
  3. 在子类的构造函数中this的使用必须在super调用后
  4. 子类可以重写父类的方法
  5. 静态成员可以被继承,同样也可以被重写

访问修饰符

TypeScript可以提供三种访问修饰符,分别是public,private,protected

其中:

  • public修饰的属性或方法是公有的,可以在任何地方被访问到,所有类中的成员默认为public
  • private修饰的属性或方法是私有的,不能在声明它的类外部进行访问
  • protected修饰的属性或方法是受保护的,它允许在基类和基类的子类中进行访问

public

类中默认所有的属性和方法都是public修饰符,因此,所有属性和方法都可以在任何地方被访问到(静态成员和实例属性均可)

例如:

1
2
3
4
5
6
7
class Person {
public name: string;
public static mes: string = 'short mes';
public constructor(name: string) {
this.name = name;
}
}

这个看起来与我们之前的例子相比只是在每个属性和方法之前添加了public修饰符而已,实际上它们的效果也是一样的。因为,类中所有属性和方法都是默认使用public修饰符的,之前只是简写罢了。

private

当成员被标记为private后,它就不能在声明它的类的外部使用了

我们将上述代码的public改为private来看下会有什么影响:

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
private name: string;
private static mes: string = 'short mes';
private constructor(name: string) {
this.name = name;
}
}

let ming = new Person('ming');
// 类“Person”的构造函数是私有的,仅可在类声明中访问
console.log(Person.mes);
// 属性“mes”为私有属性,只能在类“Person”中访问。

我们就可以看到会报出如上的错误。因此,如果类中的某个成员前添加了private修饰符,那么该成员就变为了这个类的私有成员,在类的外部是无法访问该成员的

protected

使用protected修饰符的类中成员可以在该类以及该类的派生类中进行使用

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Person {
name: string;
age: number;
private static mes: string = '这是Person的私有成员mes';
constructor(name: string,age: number) {
this.name = name;
this.age = age;
}
protected static getMes() {
console.log(this,this.mes);
}
}

class Man extends Person {
constructor(name,age) {
super(name,age);
}
public static getPersonMes() {
// 调用Person的子类Man继承得到的protected方法getMes
Man.getMes();
}
}

// 创建Man实例
let ming = new Man('ming',18);
console.log(ming);
// Man { name: 'ming', age: 18 }

// 访问Person类私有静态成员mes,则会报错
// console.log(Person.mes);

Man.getPersonMes();
// [Function: Man] 这是Person的私有成员mes

/*
在看到这段输出的时候,我产生了疑问,为什么this指向的是子类Man,而this.mes却可以得到基类中的私有属性,后来当我将这段代码编译成es6的时候就想明白了
*/

为了说明上述的问题,我将代码做了精简

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
private static mes = '这是Person基类的私有属性mes';
protected static getMes() {
console.log(this,this.mes);
}
constructor() {}
}

class Man extends Person {
constructor() {
super();
Man.getMes();
// [Function: Man] 这是Person基类的私有属性mes
}
}

let man = new Man();

当我们将该段代码编译为es6时,得到以下的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Person = /** @class */ (function () {
function () {
}
Person.getMes = function () {
console.log(this, this.mes);
};
Person.mes = '这是Person基类的私有属性mes';
return Person;
}());
var Man = /** @class */ (function (_super) {
__extends(Man, _super);
function Man() {
var _this = _super.call(this) || this;
Man.getMes();
return _this;
// [Function: Man] 这是Person基类的私有属性mes
}
return Man;
}(Person));
var man = new Man();
//# sourceMappingURL=a.js.map

当看到这段代码的时候,我就突然明白了。因为,Js的继承是通过原型链来实现的。

因此,父类Person是子类Man的原型对象。所以,当在Man中找不到属性mes的时候,它就会顺着原型链向上查找,因此在原型对象Person中找到了属性mes

readonly关键字

我们之前在接口部分接触到了readonly关键字的概念。该关键字用来表示属性是只读的,不允许被修改

例如:

1
2
3
4
5
6
class Person {
static readonly mes: string = 'short mes';
}

Person.mes = 'long mes';
// 报错: Cannot assign to 'mes' because it is a read-only property.

我们可以看到,当我们试图要修改一个readonly成员时是不被允许的

另外,值得注意的是:

如果 readonly 和其他修饰符(例如:public private protected static)同时存在的话,需要写在其后面。

存储器(getter和setter)

TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
private static _mes: string = 'short mes';

constructor() {}

static get mes() {
return this._mes;
}

static set mes(_mes: string) {
console.log('现在开始修改Person类的私有属性_mes')
this._mes = _mes;
}
}

console.log(Person.mes);
// 'short mes'
Person.mes = 'long mes';
// '现在开始修改Person类的私有属性_mes'
console.log(Person.mes);
// 'long mes'

因此,使用getter和setter方法我们就可以在值访问和值操作之前进行拦截,从而进行一些自定义操作

抽象类

有些时候,我们经常会在子类重写父类的某个方法,而且我们不会去实例化这个父类。那么,抽象类就变得很有用处了。

抽象类不允许被实例化,并且抽象类需要定义抽象方法。该抽象方法不用完整地定义方法体。

记住,抽象类不能被实例化

另外,我们需要在派生类中实现该抽象方法

我们使用abstract关键字是定义抽象类和在抽象类内部定义抽象方法。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
abstract class Person {
abstract sayHello(): void;
sayBye() {
console.log('bye');
}
}

class Man extends Person {
sayHello():void {
console.log('Man say hello');
}
constructor() {
super();
}
}

class Woman extends Person {
sayHello() {
console.log('Woman say hello');
}
constructor() {
super();
}
}

let man = new Man();
let woman = new Woman();

man.sayHello(); // 'Man say hello'
woman.sayHello(); // 'Woman say hello'
man.sayBye(); // 'bye'
woman.sayBye(); // 'bye'

给类的实例化对象加上类型

类似于给对象加上接口类型,我们也可以给类的实例化对象加上类的类型,表明该对象是由此类实例化得到的

例如:

1
2
3
4
5
6
7
8
9
class Person {
constructor() {}
sayHello() {
console.log('hello');
}
}

let ming: Person = new Person();
ming.sayHello(); // 'hello'

类与接口

我们知道接口可以用于描述一个对象的形状以及对象内部的结构,除此之外,接口还可以对类的一部分行为进行抽象

在Java之类的面向对象的语言中,接口常用于实现多继承,在ts中也可以让一个类去强制去实现某种契约

类实现接口

比如说,有Man类和Woman类,他们都可以有一个方法sayHello()。我们可以让这个方法从基类Person中继承得到,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
sayHello() {}
}

class Man extends Person{
sayHello() {
console.log('Man say hello');
}
}

class Woman extends Person{
sayHello() {
console.log('Woman say hello');
}
}

除此之外,我们也可以使用抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Person {
abstract sayHello(): void;
}

class Man extends Person{
sayHello() {
console.log('Man say hello');
}
}

class Woman extends Person{
sayHello() {
console.log('Woman say hello');
}
}

但是我们知道,类是不允许被多继承的,那么抽象类也是如此。因此,我们就可以使用接口来定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Say {
sayHello(): void;
}

class Man implements Say {
sayHello(): void{
console.log('Man say hello');
}
}

class Woman implements Say {
sayHello(): void{
console.log('Man say hello');
}
}

类实现多个接口

接口的很重要的一个功能就是用来实现多继承的。

因此,我们可以用一个类来继承多个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Say {
sayHello(): void;
}

interface Travel{
travelByBus(): void;
}

class Man implements Say,Travel {
sayHello(): void {
console.log('Man say hello');
}
travelByBus(): void {
console.log('Man travel by bus');
}
}

let man = new Man();
man.sayHello(); // 'Man say hello'
man.travelByBus(); // 'Man travel by bus'

接口继承

接口继承接口

事实上,和类一样,接口也可以实现继承。我们可以将一个接口继承另一个接口,来实现更为灵活的可重用

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Person {
sayHello(): void;
}

interface Man extends Person{
gender: string;
}

class MaleStudents implements Man {
gender: string = 'male';
sayHello(): void {
console.log('Students say hello');
}
constuctor() {}
sayNice() {
console.log('nice');
}
}

let maleStudent = new MaleStudents();
console.log(maleStudent.gender); // 'male'
maleStudent.sayHello(); // 'Students say hello'
maleStudent.sayNice(); // 'nice'

当然,既然类可以多继承多个接口,那么接口也可以多继承多个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Say {
say(): void;
}

interface Walk {
walk(): void;
}

interface Person extends Say,Walk{
property: string;
}

class Man implements Person {
property = 'person';
say() {
console.log('say');
}
walk() {
console.log('walk');
}
}

接口继承类

接口也可以继承类。

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
property: string = 'person';
sayHello(): void {
console.log('Person say hello');
}
}

interface InterfacePerson extends Person {}

class Man implements InterfacePerson {
property = 'man';
sayHello(): void {
console.log('Man say hello');
}
}

let man = new Man();
console.log(man.property); // 'man'
man.sayHello(); // 'Man say hello'