前言
书接上文,我们继续开始对TS的学习,上一次我们讲到了数组类型,那么紧接着这一次我们开始往后学习
TS的语言类型🚴♀🚴♀
类(Class)
对类这方面的学习首先我想要先复习一下类这方面的知识,
class Animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello(): void { console.log("发出动物的叫声"); } } const animal = new Animal("小蛇", 4); animal.sayHello();
- 首先在这里面我们定义了一个
Animal类
,这个类里面有两个属性分别是name和age表示动物的名称和年龄,然后这个类中还包含一个方法,这个方法可以打印一段话。 - 这个类定义完成之后,我们就可以将他实例化,然后传值进去紧接着我们就可以通过实例对象来调用这个
sayHello方法
,我们再控制台通过ts-node
(需要安装指定的npm包,第一节有讲到)这个命令去运行这个文件,控制台就会有一段话打印。 - 现在我们就算完成了一个基本的类,但是这个类太广泛了,如果我们想要具体每个不同的动物呢?比如每种动物都有不同的叫声,我们想要让他们各自实现自己的功能但是又可以基于这个Animal之上,不需要完全都是自己构造的话该如何做呢?
类的继承(extends)
class Dog extends Animal { constructor(name: string, age: number) { super(name, age); } sayHello(): void { console.log("发出汪汪的叫声"); } } const dog = new Dog("豆豆", 3); dog.sayHello();
这里面我们实现了Dog类,这个类是继承于父类Animal的,为什么要这样做呢?
- 就像之前说的,因为有一些公用的属性方法比如像name,age这些属性在父类中是直接定义好的,因此我们就没必要再重新定义一次,可以直接进行继承。
- 其次我们还在Dog类中又写了一次sayHello方法,这个其实就叫做方法的重写,如果我们不进行方法的重写,那么当我们通过dog这个实例去调用sayHello方法的时候我们还是调用的父类中的方法。因此我们要在子类中重写这个方法。
- 其次我们也会发现我们在子类中定义了
构造方法constructor
,这个构造方法中还使用了super方法,这个方法是干什么用的呢?
- 其实我们可以通过super()来调用父类的构造函数,并且可以通过super.方法/属性可以用来调用父类中的方法或者属性。
- 我们通过调用super之后,就为Dog中的属性name和age进行了赋值。
抽象类/抽象方法
abstract class Animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } abstract sayHello(): void; }
当我们在class这个类前面加上关键字abstract
之后,这个类就变成了抽象类,什么是抽象类呢?
简单讲就是无法进行实例化的类
,等于说这个类就是为了被别的类继承的(生下来就是当父亲的)
那么什么是抽象方法呢?
抽象方法其实和抽象类是类似的,他不需要有方法体(就是这个方法不需要实现出来),但是要求继承抽象类的子类必须将他实现(相信学过java的这块很好理解)
简单复习类的知识之后,进入我们要讲的正题:
使用接口interface对类进行约束
我们在之前讲过接口(interface),其实在类里面我们也可以使用接口进行约束,这里面需要使用implements
关键字。下面我们进行一下演示:
interface DogIn { init(): void; sex: string; } class Dog extends Animal implements DogIn { sex: string; constructor(name: string, age: number) { super(name, age); } sayHello(): void { console.log("发出汪汪的叫声"); } init(): void {} }
这里面我们首先定义了一个interface DogIn,然后我们让Dog类去实现这个interface,注意看我这个implements的书写位置在这个继承的类Animal之后
当我们这样做了之后,我们就必须在类里面实现sex属性和init方法,否则就会报错。
讲完这个部分之后,接下来还有一些小的点需要讲解
一些关键字的介绍
private关键字:
我们可以在父类中的方法或者属性前面加上这个关键字,当我们加上这个关键字之后,这个方法或者属性只能在这个类的内部被调用,其他的比如像继承,或者在外面实例化之后调用这个方法都是不行的,举一个例子:
class Cat { private name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } private sayHello(): void { console.log(`${this.name}发出了喵喵叫声`); } } const cat = new Cat("咪咪", 4);
当我们想要打印出来这个cat实例中的name或者age属性,或者调用sayHello方法的时候,就会访问不到这个属性或者方法因为这个是私有的。
诸如此类还有类似static
,public
,protected
等等内容
这个protected
和private有些区别在于这个protected
可以给子类使用,也可以在类的内部里面使用,相较于private
来说范围扩大了一点,但是protected
依然是无法供外部实例化之后来调用内部的属性或者方法。
get和set
接下来我们最后介绍一下class中的get和set:
其实这部分内容和我们之前学习的Object.defineProperty的原理是有些相似的,有运用了get和set方法,下面我们来举一个例子
class Xss { _value: string; constructor(value: string) { this._value = value; } get value() { return this._value + "scs"; } set value(newVal) { this._value = newVal; } } const xss = new Xss("dede"); console.log(xss.value);
当我们通过get和set设置之后,如果我们需要读取value值,那么类里面的get方法就会进行一个拦截操作。而如果对value进行一个修改的话,同样这个set方法也会进行一个拦截处理。这就是类里面的get和set方法。
到这里了类的相关所有内容就基本讲解完了。
联合类型|类型断言|交叉类型
联合类型顾名思义就是可以是多个类型,举一个例子:
let phone: number | string = 1234546;
这个是简单的联合类型,这个就表明这个phone这个变量的值可以是number类型的数据或者是string类型的数据。
对象类型的交集或者并集
interface Foo { foo: string; name: string; } interface Bar { bar: string; name: string; } const sayHello = (obj: Foo | Bar): void => {};
这就表明我们传进去的对象要是Foo类型的或者是Bar类型的,必须满足这两个类型中的一个才行(可以理解为或的意思)
交叉类型
当然我们有时候会有这种情况,比如这种:
interface IProduct { id: number; title: string; price: number; inventory: number; //库存 } type CartProduct = { num: number; } & Omit<IProduct, "inventory">;
比如现在有一个购物情景,一共有两中商品:第一种是在货架上面的商品,一种是在你购物车的商品,这商品都有一些共有的特性,但是又有些区别,我们该怎么处理比较合理呢?
就像上面的IProduct
表示的就是所有的商场中的普通的商品,有一些基本的属性(价格,名称,库存等等),当然现在我们到定义购物车中商品的属性,那么我们完全没有必要重新定义,我们可以使用&
这个符号表明将IProduct
中的属性给合并过来,当然这里由于购物车中的商品不需要inventory
这个属性,可以使用Omit
来去除。
类型断言
类型断言其实就是使TS强制的将一个类型断言为某一个类型,注意这里只是"断言",其实带有一些欺骗的性质,就是告诉编译器,我已经知道这个类型是什么了,你不用在检查了,举一个例子:
interface Foo { foo: string; name: string; } interface Bar { bar: string; name: string; } const sayHello = (obj: Foo | Bar): void => { console.log(obj.name); };
因为我们这里使用了联合类型,因此当我们访问共有的属性name的时候,是可以访问到的,但是如果我们先要访问foo或者是bar,就会提示错误。因为此时还不能确定传递过来的值到底有没有foo或者bar这个属性,当我们确定传递过来的是Foo类型的时候,这个时候就可以类型断言
console.log((obj as Foo).foo);
我们这里就是将传递过来的obj断言成了Foo类型,此时我们在打印foo就不会报错了。
但是要注意的是,如果你传递的类型不是Foo类型,那么我们在运行的时候还是会报错,因为TS此时找不到foo这个属性,而且TS不会帮你检查。
元组类型
紧接着我们开始学习元组类型
那么什么是元组类型呢?
let arr: [number, boolean] = [1, false];
比如类似上面这种,当我们在定义的数组的时候使用这种方式规定了数组中内容的类型,数量,第一个值必须是number类型而第二个值则必须是boolean类型,当然这种是简单的写法我们还可以这么写:
let arr: [x: number, y?: boolean] = [1]; let arr: [number, boolean?] = [1];
我们可以给元组中加上该类型的值是否为必选值,这里面我们定义第二个布尔值为可选的参数,这样我们在数组中就不需要强制添加第二个值。
元组中的剩余参数:
和js一样,当我们确定前面几个参数的值的类型,但是后面的值的类型是固定的,那么这个时候我们可以使用元组中的剩余参数来接收一下
let arr2: [number, ...string[]] = [1, "s", "sac"];
这个表示第一个值的类型是数组类型,剩下的我们全部都使用字符串类型。
readonly关键字
现在新版中可以确保元组中的数据无法被修改:
const point: readonly [number, number] = [10, 20];
这让任何企图修改该数组中的属性的方法操作都不能实现,就类似于const关键字
以上就是元组的基本使用
小节🤙🤙
这是TS的第二篇文章,感觉收获满满,当然还有很多知识没有提及,我们留在后面讲述