通俗易懂的 TS 基础知识总结

4,279 阅读3分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」。

本文梳理了 TS 基础知识,知识图谱如下:

image.png

比官方文档更精简,更容易理解,但基本能覆盖全日常开发需要的 TS 基础知识。

你掌握了吗?还没掌握就一起来查漏补缺吧!

TS 基础知识总结

基础类型

boolean 类型

let isHandsome: boolean = true   

赋值与定义的不一致,会报错。 image.png

number 类型

let age: number = 18   

string 类型

let realName: string = 'lin'             
let fullName: string = `A ${realName}`   // 支持模板字符串 

undefined 和 null 类型

let u:undefined = undefined              // undefined 类型
let n:null = null                        // null 类型

默认情况下 nullundefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给number 类型的变量。

let age: number = null
let realName: string = undefined

但是如果指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自,不然会报错。

image.png

any 类型

不清楚用什么类型,可以使用 any 类型。这些值可能来自于动态的内容,比如来自用户输入或第三方代码库

let notSure: any = 4
notSure = "maybe a string"     // 可以是 string 类型
notSure = false                // 也可以是 boolean 类型

notSure.name                   // 可以随便调用属性和方法
notSure.getName()

不建议使用 any,不然就丧失了 TS 的意义。

数组类型

let list: number[] = [1, 2, 3]
list.push(4)                   // 可以调用数组上的方法

数组里的项写错类型会报错

image.png

push 时类型对不上会报错

image.png

如果数组想每一项放入不同数据怎么办?用元组类型

元组类型

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

let tuple: [number, string] = [18, 'lin']

写错类型会报错:

image.png

越界会报错:

image.png

可以对元组使用数组的方法,比如使用 push 时,不会有越界报错

let tuple: [number, string] = [18, 'lin']
tuple.push(100)       // 但是只能 push 定义的 number 或者 string 类型

push 一个没有定义的类型,报错 image.png

函数类型

TS 定义函数类型需要定义输入参数类型和输出类型。

输出类型也可以忽略,因为 TS 能够根据返回语句自动推断出返回值类型。

function add(x:number, y:number):number {
    return x + y
}
add(1,2)

函数没有明确返回值,默认返回 Void 类型

void类型像是与 any 类型相反,它表示没有任何类型。

function welcome(): void {
    console.log('hello');
}

可选参数

参数后加个问号,代表这个参数是可选的

function add(x:number, y:number, z?:number):number {
    return x + y
}

add(1,2,3)
add(1,2)

函数表达式写法

let add2 = (x: number, y: number): number => {
    return x + y
}

函数赋值

函数不能随便赋值,会报错的

image.png

也可以用下面这种方式定义一个函数 add3,把 add2 赋值给 add3

let add2 = (x: number, y: number): number => {
    return x + y
}

const add3:(x: number, y: number) => number = add2

有点像 es6 中的箭头函数,但是不是箭头函数,TS 遇到 : 就知道后面的代码是写类型用的。

当然,不用定义 add3 类型直接赋值也可以,TS 会在变量赋值的过程中,自动推断类型,如下图:

image.png

interface

基本概念

interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

定义 interface 一般首字母大写,代码如下:

interface Person {
    name: string
    age: number
}

const p1: Person = {
    name: 'lin',
    age: 18
}

属性必须和类型定义的时候完全一致。

少写了属性,报错:

image.png

多写了属性,报错:

image.png

类型提示,显著提升开发效率:

image.png

注意:interface 不是 JS 中的关键字,所以 TS 编译成 JS 之后,这些 interface 是不会被转换过去的,都会被删除掉,interface 只是在 TS 中用来做静态检查。

可选属性

跟函数的可选参数是类似的,在属性上加个 ?,这个属性就是可选的,比如下面的 age 属性

interface Person {
    name: string
    age?: number
}

const p1: Person = {
    name: 'lin',
}

只读属性

如果希望某个属性不被改变,可以这么写:

interface Person {
    readonly id: number
    name: string
    age: number
}

改变这个只读属性时会报错。

image.png

interface 描述函数类型

interface 也可以用来描述函数类型,代码如下:

interface ISum {
    (x:number,y:number):number
}

const add:ISum = (num1, num2) => {
    return num1 + num2
}

自定义属性(可索引的类型)

上文中,属性必须和类型定义的时候完全一致,如果一个对象上有多个不确定的属性,怎么办?

可以这么写。

interface RandomKey {
    [propName: string]: string
}

const obj: RandomKey = {
    a: 'hello',
    b: 'lin',
    c: 'welcome',
}

如果把属性名定义为 number 类型,就是一个类数组了,看上去和数组一模一样。

interface LikeArray {
    [propName: number]: string
}

const arr: LikeArray = ['hello', 'lin']

arr[0]  // 可以使用下标来访问值

当然,不是真的数组,数组上的方法它是没有的。

image.png

duck typing(鸭子类型)

看到这里,你会发现,interface 的写法非常灵活,它不是教条主义。

用 interface 可以创造一系列自定义的类型。

事实上, interface 还有一个响亮的名称: duck typing(鸭子类型)。

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
-- James Whitcomb Riley

这句话完美地诠释了 interface 的含义,只要数据满足了 interface 定义的类型,TS 就可以编译通过。

举个例子:

interface FunctionWithProps {
    (x: number): number
    name: string
}

FunctionWithProps 接口描述了一个函数类型,还向这个函数类型添加了 name 属性,这看上去完全是四不像,但是这个定义是完全可以工作的。

const fn: FunctionWithProps = (x) => {
    return x
}

fn.name = 'hello world'

这就是 interface,非常的灵活。

我们知道, JS 是靠原型和原型链来实现面向对象编程的,es6 新增了语法糖 class。

TS 通过 publicprivateprotected 三个修饰符来增强了 JS 中的类。

在 TS 中,写法和 JS 差不多,只是要定义一些类型而已,我们通过下面几个例子来复习一下类的封装、继承和多态。

基本写法

定义一个 Person 类,有属性 name 和 方法 speak

class Person {
    name: string
    constructor(name: string) {
        this.name = name
    }
    speak() {
        console.log(`${this.name} is speaking`)
    }
}

const p1 = new Person('lin')      // 新建实例  

p1.name                           // 访问属性和方法
p1.speak()

继承

使用 extends 关键字实现继承,定义一个 Student 类继承自 Person 类。

class Student extends Person {
    study() {
        console.log(`${this.name} needs study`)
    }
}

const s1 = new Student('lin')

s1.study()

继承之后,Student 类上的实例可以访问 Person 类上的属性和方法。

image.png

super关键字

注意,上例中 Student 类没有定义自己的属性,可以不写 super ,但是如果 Student 类有自己的属性,就要用到 super 关键字来把父类的属性继承过来。

比如,Student 类新增一个 grade(成绩) 属性,就要这么写:

class Student extends Person {
    grade: number
    constructor(name: string,grade:number) {
        super(name)
        this.grade = grade
    }
}

const s1 = new Student('lin', 100)

不写 super 会报错。

image.png

多态

子类对父类的方法进行了重写,子类和父类调同一个方法时会不一样。

class Student extends Person {
    speak() {
        return `Student ${super.speak()}`
    }
}

public

public,公有的,一个类里默认所有的方法和属性都是 public。

比如上文中定义的 Person 类,其实是这样的:

class Person {
    public name: string
    public constructor(name: string) {
        this.name = name
    }
    public speak() {
        console.log(`${this.name} is speaking`)
    }
}

public 可写可不写,不写默认也是 public。

private

private,私有的,只属于这个类自己,它的实例和继承它的子类都访问不到。

将 Person 类的 name 属性改为 private。

class Person {
    private name: string
    public constructor(name: string) {
        this.name = name
    }
    public speak() {
        console.log(`${this.name} is speaking`)
    }
}

实例访问 name 属性,会报错:

image.png

继承它的子类 访问 name 属性,会报错:

image.png

protected

protected 受保护的,继承它的子类可以访问,实例不能访问。

将 Person 类的 name 属性改为 protected。

class Person {
    protected name: string
    public constructor(name: string) {
        this.name = name
    }
    public speak() {
        console.log(`${this.name} is speaking`)
    }
}

实例访问 name 属性,会报错:

image.png

子类可以访问。

class Studeng extends Person {
    study() {
        console.log(`${this.name} needs study`)
    }
}

static

static 是静态属性,可以理解为是类上的一些常量,实例和子类都不能访问。

比如一个 Circle 类,圆周率是 3.14,可以直接定义一个静态属性。

class Circle {
    static pi: 3.14
    public radius: number
    public constructor(radius: number) {
        this.radius = radius
    }
    public calcLength() {
        return Circle.pi * this.radius * 2  // 计算周长,直接访问 Circle.pi
    }
}

实例访问,会报错:

image.png

interface 和 class 的关系

上文中我们说过,interface 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

interface 同样可以用来约束 class,要实现约束,需要用到 implements 关键字。

implements

implements 是实现的意思,class 实现 interface。

比如手机有播放音乐的功能,可以这么写:

interface MusicInterface {
    playMusic(): void
}

class Cellphone implements MusicInterface {
    playMusic() {}
}

定义了约束后,class 必须要满足接口上的所有条件。

如果 Cellphone 类上不写 playMusic 方法,会报错。

image.png

处理公共的属性和方法

不同的类有一些共同的属性和方法,使用继承很难完成。

比如汽车(Car 类)也有播放音乐的功能,你可以这么做:

  • 用 Car 类继承 Cellphone 类
  • 找一个 Car 类和 Cellphone 类的父类,父类有播放音乐的方法,他们俩继承这个父类

很显然这两种方法都不合常理。

实际上,使用 implements,问题就会迎刃而解。

interface MusicInterface {
    playMusic(): void
}

class Car implements MusicInterface {
    playMusic() {}
}

class Cellphone implements MusicInterface {
    playMusic() {}
}

这样 Car 类和 Cellphone 类都约束了播放音乐的功能。

再比如,手机还有打电话的功能,就可以这么做,Cellphone 类 implements 两个 interface。

interface MusicInterface {
    playMusic(): void
}

interface CallInterface {
    makePhoneCall(): void
}

class Cellphone implements MusicInterface, CallInterface {
    playMusic() {}
    makePhoneCall() {}
}

这个 CallInterface 也可以用于 iPad 类、手表类上面,毕竟他们也能打电话。

interface 来约束 class,只要 class 实现了 interface 规定的属性或方法,就行了,没有继承那么多条条框框,非常灵活。

约束构造函数和静态属性

使用 implements 只能约束类实例上的属性和方法,要约束构造函数和静态属性,需要这么写。

以我们上文提过的 Circl 类为例:

interface CircleStatic {
    new (radius: number): void
    pi: number
}

const Circle:CircleStatic = class Circle {
    static pi: 3.14
    public radius: number
    public constructor(radius: number) {
        this.radius = radius
    }
}

未定义静态属性 pi,会报错:

image.png

constructor 入参类型不对,会报错:

image.png

结尾

至此,TS 基础部分就总结完了,再把 泛型常用高级特性 总结完,应付开发就完全没问题了。

如果我的文章对你有帮助,你的赞就是对我最大的支持^_^

未完待续。。。

往期

TS 中 interface 和 type 究竟有什么区别?