简介
传统的面向对象语言基本都是基于类的,JavaScript
基于原型的方式让开发者多了很多理解成本,在 ES6
之后,JavaScript
拥有了 class
关键字,虽然本质依然是构造函数,但是开发者已经可以比较舒服地使用 class
了。
在TypeScript
中,对类有了更一步的提升,比如添加了访问修饰符public、private、protected
,有了抽象类的概念等等。
定义类
我们使用关键字class
定义类。
class People {
// 属性
name: string
constructor(name: string) {
this.name = name
}
// 方法
say() {
console.log(this.name)
}
}
在类中我们可以定义属性和方法,并且可以在构造函数中给属性赋初始值。
上面的例子我们首先定义了name
属性,然后再在构造函数中给该属性赋值。如果不想这么麻烦的话,我们还可以简写。
直接定义在构造函数中就可以了。
class People {
constructor(public name: string) {
this.name = name
}
say() {
console.log(this.name)
}
}
我们知道,class
其实就是function
的语法糖。
console.log(typeof People) //function
那我们使用class
定义类和之前的方法定义构造函数有什么区别呢?
let Animal = function (type) {
// 定义属性和方法
this.type = type;
this.say = function () {
console.log("say");
};
};
// 在原型上定义属性和方法
Animal.prototype.info = '动物'
Animal.prototype.walk = function () {
console.log("walk");
};
const a1 = new Animal("dog");
console.log(a1);
我们会发现,在构造函数里面定义的方法和属性都会挂载在实例上,如果需要挂载在原型上需要我们使用prototype
显示定义。
我们再来看看class
class Animal {
type: string;
say: () => void;
constructor(type: string) {
this.type = type;
this.say = function () {
console.log("say");
};
}
walk() {
console.log("walk");
}
}
const a1 = new Animal("dog");
console.log(a1);
我们可以发现,在constructor
定义的属性和方法都会挂载在实例上,跟function
是一样的。
但是定义在类中的方法,默认会被挂载到原型上,不再需要我们显示定义在prototype
上。
继承
在之前js
的继承是很麻烦的,有各种版本的继承,什么构造继承、原型继承、组合继承等等。不清楚的可以看看笔者前面写的都2022年了你不会还没搞懂JS原型和继承吧。
ES6
出现了class
后,继承就相对简单啦。我们只需要使用extends
关键字就能实现继承。
class People {
name: string
constructor(name: string) {
this.name = name
}
say() {
console.log(this.name)
}
}
// 继承
class Child extends People {
// 不显示定义构造函数 默认会调用父类构造函数
// 如果显示定义了构造函数,就必须使用super调用父类构造函数
// 父构造函数会比子类构造函数先运行
constructor(name: string) {
super(name)
}
}
const c1 = new Child("randy")
c1.name // randy
c1.say() // randy
请注意,类不比接口,不能多继承,一次只能继承一个类。
下面我们来说说TS
对类新增的访问修饰符。
访问修饰符
学过java
的同学对访问修饰符肯定不陌生,在TS
中概念和使用是类似的。
访问修饰符总共有省略、public、protected、private
四种,只是省略和public
是一样的。
public
public
是公开的,也就是用public
定义的属性和方法在外部是可以被访问到的。
public
权限我们一般会把public
省略,当然你写上也是一样的。
class People1 {
// 公共属性
name: string;
constructor(name: string) {
this.name = name;
}
// 公共方法
say() {
console.log("say");
}
// 公共方法
public say1() {
console.log(this.name + " say");
}
}
const p1 = new People1('randy')
p1.name // randy
p1.say() // say
p1.say1() // randy say
protected
protected
定义的属性或方法只能在本类或子类中访问到。
就算是本身实例或子实例也访问不到。
class People {
name: string
protected count: number
constructor(name: string) {
this.name = name
}
sayCount() {
// 本类能访问到
console.log(this.count)
}
}
// 继承
class Child extends People {
constructor(name: string) {
super(name)
}
sayCount2() {
// 子类能访问到
console.log(this.count)
}
}
const p1 = new People("demi")
p1.count // Error 访问不到
const c1 = new Child("randy")
c1.count // Error 访问不到
private
private
是私有的意思,就是只能在本类中访问。
class People1 {
name: string;
private _sex: string;
constructor(name: string) {
this.name = name;
}
}
const p1 = new People1('randy')
p1._sex // Error 属性“_sex”为私有属性,只能在类“People1”中访问
那想要私有属性能被设置或访问怎么办呢?这就需要用到我们的get
和set
方法。当提供了get
方法后我们就能访问,当提供了set
方法后我们就能设置新值。
class People1 {
name: string;
private _sex: string;
constructor(name: string) {
this.name = name;
}
get sex() {
return this._sex;
}
set sex(val) {
this._sex = val;
}
}
const p1 = new People1('randy')
p1.sex = 'male'
console.log(p1.sex) // male
静态属性和静态方法
我们知道,静态属性和方法是类所有的,只能使用类来访问,不能通过实例来访问。
在ES6
之前我们通过给构造函数直接定义属性和方法就是静态属性和静态方法。
let Animal2 = function (type) {
this.type = type;
};
// 定义静态属性和静态方法
Animal2.count = 1;
Animal2.say = () => {
console.log("say");
};
const a2 = new Animal2("cat");
console.log(Animal2.count); // 1
console.log(Animal2.say()); // say
// console.log(a2.count); // Error
// console.log(a2.say()); // Error
在class
中,我们使用static
来定义静态属性和静态方法。
class Animal2 {
public type: string;
public static count: number = 1;
constructor(type: string) {
this.type = type;
}
public static say() {
console.log("say");
}
}
const a2 = new Animal2("cat");
console.log(Animal2.count); // 1
console.log(Animal2.say()); // say
// console.log(a2.count); // Error
// console.log(a2.say()); // Error
上面的例子我们在static
前面加上了public
,其实你省略也是可以的。因为省略和public
的权限是一样的。当然你还可以使用 propected、private
来修饰,这个看具体应用场景。
只读属性
类似接口,我们还可以使用readoly
来定义只读属性。
class People1 {
public readonly num: number = 10;
}
const p1 = new People()
console.log(p1.num) // 10
p1.num = 100 // Error 无法分配到 "num" ,因为它是只读属性。
上面的例子我们在readonly
前面加上了public
,其实你省略也是可以的。因为省略和public
的权限是一样的。当然你还可以使用 propected、private
来修饰,这个看具体应用场景。
注意,我们只能使用
readoly
来定义只读属性,不能定义只读方法。
可选属性
类似接口,我们还可以使用?
来定义可选属性。
class People {
name: string;
age?: number;
constructor(name: string, age?: number, public sex?: string) {
this.name = name;
this.age = age;
}
}
const p1 = new People("randy");
const p2 = new People("randy", 24);
const p3 = new People("randy", 24, "male");
抽象类
抽象类做为其它派生类的基类使用,它不能直接被实例化,不同于接口,抽象类可以包含成员的实现细节。但是接口是只能有定义,不能有实现。
抽象类用abstract
修饰。在抽象类中我们可以使用abstract
定义抽象属性和抽象方法。抽象属性和抽象方法是不需要我们具体去实现的,只要定义即可。
abstract class A1 {
// 抽象属性
abstract age: number;
sex: string = "male";
say() {
console.log("say");
}
// 抽象方法
abstract hi(): void;
abstract hi2(num: number): number;
// abstract hi2: (num: number) => number;
}
我们继承抽象类的时候,必须把里面的抽象属性和抽象方法全都实现,不然会报错。
class Son extends A1 {
age: number;
hi() {
console.log("hi");
}
hi2(num: number) {
return num + 1;
}
}
const s1 = new Son();
console.log(s1.age);
console.log(s1.sex);
s1.say(); // say
s1.hi(); // hi
s1.hi2(1); // 2
接口实现
前面我们讲接口的时候有提到过一嘴,接口是可以被类实现的。
我们来看例子
interface Inter1 {
name: string;
hello(): void;
// hello: () => void
}
我们使用implements
关键字来实现接口。接口里面定义的属性和方法我们必须全部都实现,否则会报错。
class Son implements Inter1 {
name: string;
hello() {
console.log("hello");
}
}
const s1 = new Son();
console.log(s1.name);
s1.hello();
不比类只能继承一个,接口是可以被多实现的。
interface Inter2 {
name2: string;
hello2: () => void;
}
多实现,使用逗号分开
class Son implements Inter1, Inter2 {
name: string;
name2: string;
hello() {
console.log("hello");
}
hello2() {
console.log("hello2");
}
}
const s1 = new Son();
console.log(s1.name);
s1.hello();
console.log(s1.name2);
s1.hello2();
类、抽象类、接口的区别
说到这,我们学习了类、抽象类、接口,有些同学可能有点懵了,这三者到底都有什么区别呢?
下面笔者说说自己的见解。
类是对对象的抽象
类里面可以定义属性和方法,并且属性有值,方法有具体的实现,实例化出来是一个一个的具有属性和方法的对象。
抽象类是对类的抽象
抽象类不能被实例化。它里面既可以有抽象属性和抽象方法又可以有普通属性和普通方法。我们可以在抽象类中把通用行为定义成普通属性和普通方法。把一些灵活多变的属性和方法定义为抽象属性和抽象方法,在子类中根据不同场景去具体的实现。
抽象类可以被继承,只能单继承。
抽象类可以继承抽象类,并且不需要把抽象属性和方法实现。
抽象类可以继承普通类。
接口是对行为的抽象
接口不能被实例化。接口里面只能定义属性和方法,属性不能被赋值,方法不能有具体实现,相当于就只能有抽象。相比抽象类更加严格。
接口可以被类实现,并且可以多实现。
接口可以继承接口,并且支持多继承。
系列文章
TypeScript入门之类型推断、类型断言、双重断言、非空断言、确定赋值断言、类型守卫、类型别名
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!