前言
在JavaScript中,其实是没有其他语言中类的概念的,即使是ES6的class,不过也是Propotype的语法糖,而在TypeScript中,它对类这个概念,进行了一定程度的加强,来更好的约束我们的Javascript,用法基本上和ES6的class大同小异; ?
基础用法
先来定一个最简单的类
class Human {
}
这就是简单的一个类型,如果要在类中加上属性和方法,如下
class Human {
name = "oliver";
getName() {
return this.name;
}
}
同样,在Ts中的类和ES6语法中的类一样,可以继承
class Human {
name = "oliver";
getName() {
return this.name;
}
}
class User extends Human {
age = 18;
getAge() {
return this.age;
}
}
const user = new User();
console.log(user.getName())
打印出来的结果是来自于上级的oliver,另外这个上级我们称作为父类,那么这样便实现了继承,另外如果子类的属性或方法和父类重叠了,那么子类的属性或方法会覆盖父类的属性或方法,比如:
class Human {
name = "oliver";
getName() {
return this.name;
}
}
class User extends Human {
age = 18;
getAge() {
return this.age;
}
getName() {
return "test";
}
}
const user = new User();
console.log(user.getName())
那么如果要在子类的方法中使用父类的方法,有没有办法,比如,在子类的getName中返回父类的getName的值,那么就需要使用到另外一个关键字,super,在子类方法中使用super就相当于在使用父类:
class Human {
name = "oliver";
getName() {
return this.name;
}
}
class User extends Human {
age = 18;
getAge() {
return this.age;
}
getName() {
return super.getName();
}
}
const user = new User();
console.log(user.getName());
?
访问类型
在Ts中,类的常见的访问类型一般就是三种:私有类型-private,保护类型-protected,公共类型-public;
私有类型(private)
简单的说,就是一旦声明这个属性或方法为私有类型,那么就代表这个属性或方法仅仅能在这个类的内部使用,子类使用extends是继承不到这个私有类型的,示例如下:
class Human {
private name = "oliver";
public getName() {
return this.name;
}
}
class User extends Human {
public age = 18;
public getAge() {
return this.age;
}
public getName() {
return super.name;
}
public getNameFunc() {
return super.getName();
}
}
const user = new User();
console.log(user.name);
console.log(user.getName());
通过示例可以知道,一旦属性被定义成了private,那么这个属性无法被子类继承到,也无法被实例化的对象使用到,这个属性仅仅能被定义private属性的类使用;
公共类型(public)
这个类型通过名字就可以知道,是公共的,可以任意地方调用的,包括类内和类外,实际上,所有我们没有定义类型的属性或方法,它在内部都会给其一个默认的类型:public,比如上例的这段代码:
class Human {
name = "oliver";
getName() {
return this.name;
}
}
class User extends Human {
age = 18;
getAge() {
return this.age;
}
getName() {
return super.getName();
}
}
const user = new User();
console.log(user.getName());
这段代码中的两个类都没有使用到public,但是实际上,等同于下面这段代码:
class Human {
public name = "oliver";
public getName() {
return this.name;
}
}
class User extends Human {
public age = 18;
public getAge() {
return this.age;
}
public getName() {
return super.getName();
}
}
const user = new User();
console.log(user.getName());
也正是因为使用了public这个关键词,所以,我们在实例化后,仍然可以访问类上面的属性和方法; ?
保护类型(protected)
保护类型则是介于public和private之间,它代表,它可以被子类继承,但是无法被实例化对象继承,比如:
class Human {
protected name = "oliver";
public getName() {
return this.name;
}
}
class User extends Human {
public age = 18;
public getAge() {
return this.age;
}
public getName() {
return super.name;
}
public getNameFunc() {
return this.name();
}
}
const user = new User();
console.log(user.name);
console.log(user.getName());
实例化对象中无法调用类上定义的protected的属性或方法,但是子类上是能继承的,子类中的使用和public属性没有区别,这个就是protected; ?
小结
总的来说:、
- public类型:本类中可以使用,子类中可以使用,实例对象可以使用;
- private类型:本类中可以使用,子类中不能使用,实例对象不可以使用;
- protected类型:本类中可以使用,子类中可以使用,实例对象不可以使用;
如果还不清晰,那么看一个例子:
class Human {
protected name = "oliver";
private sex = "男";
public getName() {
return this.name;
}
}
class User extends Human {
public age = 18;
public getAge() {
return this.age;
}
public getName() {
return super.name;
}
public getSex() {
return super.sex;
}
protected getNameFunc() {
return super.getName();
}
}
const user = new User();
console.log(user.name);
console.log(user.getName());
console.log(user.sex;
构造器
构造器,也就是contructor,它是一种用于创建和初始化class创建的对象的特殊方法,先看个例子:
class Human {
public name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new Human("oliver");
console.log(user.name);
这里定义了一个类,class,它有一个私有类型的属性name,name的类型是string,并且在构造器中对其进行了初始化,初始化的值来自于new时候传入的参数,另外在TS中是可以简写的,下面这段代码和上例效果完全相同
class Human {
constructor(public name: string) {}
}
const user = new Human("oliver");
console.log(user.name);
那么,子类能继承到这个属性吗,答案是可以的,先再看下面这一个例子:
class Human {
constructor(public name: string) {}
}
class User extends Human {
getName() {
return this.name;
}
}
const user = new User("oliver");
console.log(user.getName());
因为是public属性,那么子类是可以继承到name属性的,这么写没问题,那么子类如果也有构造器怎么办?那么再看一个例子:
class Human {
constructor(public name: string) {}
}
class User extends Human {
constructor(public age: number, name: string) {
super(name);
}
getName() {
return this.name;
}
getAge() {
return this.age;
}
}
const user = new User(20, "oliver");
console.log(user.getName());
console.log(user.getAge());
这个例子中存在子类和父类,两个类都有构造器,特别的是,在子类的构造器constructor中使用了一个super函数,这就有点奇怪了,这个用法和上文中的不一样,上文的用法是super.getName()这种,这里为啥是一个函数,实际上,super在方法中使用,代表的是父类,用法就是类似于super.getName(),而在构造函数中,它就可以用作一个函数,代表的是父类的contructor函数,这样我们就可以将一些初始化的值赋予父类使用了,否则父类没办法接受实例化对象的参数; ?
到这里,又有一个新问题,如果父类的constructor中没有参数,那么怎么使用,实际上这种只要使用一下super就好了,比如:
class Human {}
class User extends Human {
constructor(public age: number, public name: string) {
super();
}
getName() {
return this.name;
}
getAge() {
return this.age;
}
}
const user = new User(20, "oliver");
console.log(user.getName());
console.log(user.getAge());
Getter和Setter
getter和setter,主要用途是对私有属性进行读取和设置,可能有同学会问,这个有什么用,既然都设置成私有属性了,那么就是希望不对它进行修改,怎么说呢,实际上是不对的,私有属性的作用却是是为了不将属性直接对外暴露,但是不代表某些情况下不能对他进行修改,因此Getter和Setter还是很有必要的,直接看个例子吧:
class User {
constructor(private _age: number, private _name: string) {}
get name() {
return this._name;
}
get age() {
return this._age;
}
}
const user = new User(20, "oliver");
console.log(user.name);
console.log(user.age);
这个就是Getter的用法,例子中定义了两个私有属性_age和_name,另外定义了两个getter,分别获取这两个私有属性,就这么简单,同理Setting也是这么简单;
class User {
constructor(private _age: number, private _name: string) {}
get name() {
return this._name;
}
get age() {
return this._age;
}
set name(value: string) {
this._name = value;
}
set age(value: number) {
this._age = value;
}
}
const user = new User(20, "oliver");
user.age = 18;
user.name = "demo";
console.log(user.name);
console.log(user.age);
值得注意的是,Getter和Setter看上去好像是一个方法,但实际上是属性,因此在调用的时候不需要加括号,以及,set中设置return是不会生效的;
静态属性和静态方法
static,这又是一个新的关键词,在MDN上的解释:类(class)通过 static 关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。这些通常是实用程序方法,例如创建或克隆对象的功能。 什么意思呢,简单的说,就是这个方法不能被实例调用,只能在类上调用,讲到这里,有些同学可能会问这个和protected感觉好像,但实际上,这两个可以说完全不是一个东西,举个例子
class User {
protected static name: string;
}
可以看出,私有类型和静态属性是可以并存的,这两者不是一个东西,值得注意的是,所有的访问类型,都是实例化后才生效的,也就是说,如果不实例化而是直接调用,public,private这些没有意义; ?
接着再说上面这段代码,真要去使用的时候会发现报错,提示:静态属性“name”与构造函数“User”的内置属性函数“name”冲突,好家伙,恭喜你,找到一个保留字,不知道为啥,我个人查了许久没有找到答案,网上流传的解释是:static下的name是一个保留词,不能使用(如果有人找到了合理的答案,记得告诉博主); ?
接着说static,我们还是直接看一个示例吧,题目:有一个构造函数,它只能被new一次,也就是设计模式中的单例模式,很多库文件就是基于单例模式实现的,比如VueX,VueX使用的就是单例模式,只有第一次使用的使用才会注入一个store,如果被多次Vue.use(vuex),那么它不会被多次覆盖生效,达到的效果如下
解答:
class User {
private static User: User;
private constructor() {}
static init() {
if (!this.User) {
this.User = new User();
}
return this.User;
}
}
const user1 = User.init();
const user2 = User.init();
console.log(user1 === user2);
只有第一次调用init的时候才会去进行new,第二次开始,都是将其已经new好的实例返回回去,这样就达到了单例模式; ?
只读属性
好吧,有一个新关键字,readonly,根据字面意思,代表着只读属性,用法也比较简单,直接看例子:
class User {
private readonly name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User("oliver");
user.name = "demo";
可以看出,尝试修改的时候会直接报错,只读属性是不允许被修改的; ?
抽象类
关键字:abstract,使用abstract定义的类就是抽象类,抽象类和普通类最大的区别就是抽象类不能被实例化,也就是不能被new,比如:
abstract class Human {}
new Human();
那么抽象类的作用是什么,主要的作用是抽离贡性的东西,比如:
abstract class Human {
abstract getSex(): string;
}
class Man extends Human {
getSex() {
return "男";
}
}
class Woman extends Human {
getSex() {
return "女";
}
}
我们定义了一个抽象类Human,他里面有一个抽象方法,规定了所有继承这个抽象类的类都必须包含getSex这个方法,并且这个方法返回的类型是字符串,讲到这里,有的同学可能会问,这个和接口Interface好像, 用法几乎一样,没错,是挺像的,区别在于,抽象类是对类进行共性的剥离,并且抽象类的内部也是可以存在具体实现的功能的代码的,比如:
abstract class Human {
getPlace(){
return "江苏"
}
abstract getSex(): string;
}
class Man extends Human {
getSex() {
return "男";
}
}
class Woman extends Human {
getSex() {
return "女";
}
}
这里的getPlace就是一个具体的实现代码,继承Human的Man和Woman都会存在这个方法,而interface,它仅仅是一个规范,它内部存在的都是对代码的约束规则,它内部实际上是不存在实际代码的;这是最大的区别,但如果仅仅使用抽象类作为一种规范,它内部不存在具体的实现代码,那它和interface实际上没什么区别,都是一种对代码的约束规范; ?
小结
本文主要讲述了:类的定义,它的基本用法,然后讲了内部存在的三种访问类型,以及当new的时候回使用到constructor这个构造器,接着又描述了Getter和Setter,静态属性,只读属性,最后描述了抽象类这个和接口很近似的存在;
|