对象的设计图:类
在ES5中,只有对象的概念,对象是通过构造函数创建出来的,构造函数本身也是对象。所以说JavaScript是一种基于对象的语言。
ES6之后,有了class,也就是我们的类,虽然class实质上是 JavaScript 现有的基于原型的继承的语法糖。但我们同样可以借鉴C#语言的面向对象的思想去理解它。让我们够使用==基于类的面向对象的方式==。
类就是对象的设计图纸,上面记录着一个事物应该有哪些字段,哪些方法,但类不是个对象,只是对象的定义。通过new 一个类,我们才能得到一个对象,这个对象有类上描述的所有成员。
这就是基于类的面向对象的方式。程序员画好了许许多多的设计图(类)。然后new 它们,形成了一个一个的对象,对象之间彼此关联,协作。我们的程序就这样运行了。
所以我们有了类的概念,通晓类与对象的关系后,这会影响我们的编程思维方式,需要什么东西,先定义个类,在new出个对象,让其工作。我们的对象更加具象了,它有类给予它的定义。
认识类
下面我们就来认识TypeScript中的类
定义类
class Person {
}
类中的成员
类内的成员有:字段、存取器、构造函数、方法。
字段
class Person {
age: number; // 这就是一个字段
}
类中的变量成员。可以被实例化的对象设置和读取。也可以被类内其他成员设置和读取。
存取器
class Person {
private _age: number;
get age(): number {
return this._age;
}
set age(newName: number) {
if(newName > 150){
this._age = 150;
}
this._age = newAge;
}
}
这里的get/set方法,就是_age变量的存取器。首先我们为_age变量添加一个private修饰符。表示它是个私有变量,禁止外部对它的访问。
const person = new Person();
person._age // 这句会报错,TS不允许我们读取私有变量。
要想使用_age,需要利用存取器:
const person = new Person();
person.age = 15; // 使用set age方法
const age = pereson.age; // 使用get age方法
使用存取器的好处是对变量的访问和设置,有了控制。原则上类内部的变量,外部是不能访问的,这就是封装性。要想访问,只能通过存取器方法。
构造函数
class Person {
private _age: number;
constructor(age: number) {
this._age = age;
}
}
constructor就是类的构造器,他的使用如下:
const person = new Person(15);
构造器就是我们在new一个类的时候,调用的方法。
方法
JavaScript中,都叫函数,但是有了类之后,类中的函数成员,就叫方法。
class Person {
eat() {
....
}
drink() {
}
}
这里的eat和drink,就是Person类的方法。
this关键字
和原生js中捉摸不定的this不同,类中的this,非常好理解,它表示当前类的实例。
类的继承
继承,本质上讲是一种代码重用机制。 类的继承是指一个类继承另一个类,那么这个类的实例上,也会有被继承类的属性和方法。
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
extends关键字就是指Dog类继承自Animal类。 Animal叫做父类。Dog叫做子类。
说到类的继承,同样带来了一些问题,需要我们了解一下。
- 父类中有的方法,子类也有,会怎么样?
- 子类想使用父类的构造函数,怎么办?
重写
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
move(distanceInMeters: number = 0) {
console.log(`Dog moved ${distanceInMeters}m.`);
}
bark() {
console.log('Woof! Woof!');
}
}
const dog: Animal = new Dog();
dog.move(10); // Dog moved 10
Dog类对Animal的move类进行了重写,调用Dog实例的move方法时,会调用Dog类重写的move方法。
super关键字
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
}
class Dog extends Animal {
constructor(name: string) { super(name); }
}
如果子类想使用父类中构造函数的逻辑,在子类的构造函数中,super()就是指父类构造函数。
访问修饰符
TypeScript还为我们提供了类成员的访问修饰符。public,private,protected。代表了成员的三个不同的访问级别。
- public:默认的访问级别,不写默认就是public。
- private: 只能被类内部访问,不能被类外部访问。
- protected: 只能被类内部,继承的类内部访问,不能被外部访问。
使用方式:
class Person {
private name: string;
protected age: string;
public height: string;
}
const john = new Person("John");
john.name; // 报错
jonh.age; // 报错
jonh.height; // 正确
上面看到private和protected在类的外部,都不可以访问,public的可以。那么private和protected有什么区别呢?
class Employee extends Person {
getInfo() {
this.name; // 报错
this.age; // 正确
}
}
区别就在于在继承类中,protected的成员,仍然可以被访问到,但是private的不行。
只读属性
可以使用readonly关键字将属性设置成功只读,相当于类字段的const。
class Person {
readonly name: string = 'john'; // 我只读
constructor(theName: string) {
this.name = theName; // 可以
}
setName() {
this.name = 'lili'; // 报错
}
}
只能在初始化和构造函数中赋值,其他地方不允许赋值。
静态属性
类中的成员还可以用static关键字来修饰,那么它就成了所有实例共有的成员。
class Person {
static globalPeopleCount: number = '7000000000';
}
const john = new Person();
john.globalPeopleCount += 1;
const lili = new Person();
lili.globalPeopleCount // 7000000001
抽象类
抽象类是一种不能被实例化的类,它的目的就是用来继承的,抽象类里面可以有抽象的成员,就是自己不实现,等着子类去实现。
抽象类和抽象成员,都是用abstract修饰:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
抽象类中还是可以有具体实现的,这样子类如果不实现,可以继承抽象类中的实现。
结束语
学习TS中的类,理解基于类的面向对象思维,可以帮助我们更好的应用面向对象思维,去解决项目的问题。