likes
comments
collection
share

TS的重新学习(二)

作者站长头像
站长
· 阅读数 14

前言

书接上文,我们继续开始对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方法的时候,就会访问不到这个属性或者方法因为这个是私有的。

诸如此类还有类似staticpublicprotected等等内容

这个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的第二篇文章,感觉收获满满,当然还有很多知识没有提及,我们留在后面讲述

转载自:https://juejin.cn/post/7276366126204452925
评论
请登录