ts 中的类通过 class 关键字创建。

class Person {
    age: number
    gender = '男'
}

使用构造函数 constructor 初始化属性。

class Person2 {
    age: number
    gender: string

    // 实例属性初始化
    constructor(age: number, gender: string) {
        this.age = age
        this.gender = gender
    }
}

const p1 = new Person2(12, '男')
console.log(p1);

类中的实例方法

class Point {
    x = 10
    y = 10

    scale(n: number) {
        this.x *= n
        this.y *= n
    }
}

const point = new Point()
point.scale(10)

console.log(point);

类的继承

继承使用 extends 关键字,使得子类拥有父类的属性方法(不是全部)。

class Animal {
    move() {
        console.log('moving along');
    }
}

class Dog extends Animal {
    name = '柯基'
    bark() {
        console.log('汪');
    }
}

const dog = new Dog()
console.log(dog.name);

dog.move()
dog.bark()

类的实现

类的实现使用 implements 关键字,接口中的属性必须要在实现类中定义,方法必须要在实现类中实现。

interface Singable {
    sing(): void
    name: string
}

class Person3 implements Singable {
    name: string = 'jack'
    sing(): void {
        console.log('鸡你太美~');
    }
}

const p4 = new Person3()
console.log(p4.name);
p4.sing()

readonly 关键字

readonly 关键字只能用户属性中,和 Java 中的 final 关键字效果类似,也就是只读。

class Person4 {
    readonly age: number = 18
    constructor(age: number) {
        this.age = age
    }
}

当不加类型时,ts 会推断成 18 的字面量类型,只要是 readonly 修饰的属性最好是要指定其类型。

class Person5 {
    readonly age = 18
}

成员可见性

目前有三种 public(默认可见性)、protected、private。上述的实例都是 public 类型,成员的属性和方法都可以访问到,这里就不再展示。

protected 受保护的属性和方法只有自己或者父类中受保护的成员,但是对实例是不可见的。

private 私有属性和方法只对自己可见。

class Animal3 {
    protected move() {
        console.log('moving along');
    }
    run() {
        console.log('跑起来');
        this.move()
        this.__run__()
    }
    private __run__() {
        console.log('内部辅助韩式');
    }
}

class Dog3 extends Animal3 {
    name = '柯基'
    private bark() {
        console.log('汪');
        this.move()
        // this.__run__() // 不能访问到父类的私有方法,报错
    }
}

const d3 = new Dog3()
// d3.move() // 受保护的属性不能在实例中访问,会报错

兼容性

兼容性又有类兼容性、接口的兼容性和函数参数兼容性。

比如我们迭代数组里的元素时,用到 forEach 方法,我们在写回调时可以有三种写法。

let arr = ['a', 'b', 'c']
arr.forEach(item => {})
arr.forEach((item, index) => {})
arr.forEach((item, index, array) => {})

类兼容性

对于类和接口类型来说,y 的成员至少与 x 相同,则 x 兼容 y( 成员多的可以赋值给少)。

两个类的属性相同

class Point2 {
    x: number
    y: number
}

class Point2D {
    x: number
    y: number
}

const point2: Point2 = new Point2D() 

多的给少的

class Point3D {
    x: number
    y: number
    z: number
}
class Point2D {
    x: number
    y: number
}

const point3: Point2D = new Point3D()
const point4: Point3D = new Point2D() // 错误的

接口兼容性

interface Point2D {
    x: number
    y: number
}

interface Point3D {
    x: number
    y: number
    z: number
}

let pp2: Point2D
let pp3: Point3D

pp2 = pp3 // 正确
pp3 = pp2 // 错误

函数参数兼容性

看对象中的属性,属性少的分配给属性多的。和上面正好反过来。

type F1 = (a: number) => void
type F2 = (a: number, b: number) => void

let f1: F1 = (a) => console.log();
let f2: F2 = f1

函数的参数为对象时,看对象中的属性,属性少的分配给属性多的。

返回值的兼容性按照其返回值本身,如果是基本类型必须要相等。如果是对象,则按照对象的兼容性规则即可。

交叉类型

将接口中定义的部分使用“&”来将两个类型合并。

interface Person7 {
    name: string
    say(): number
}

interface Contact {
    phone: string
}

type PersonDetail = Person7 & Contact
let obj1: PersonDetail = {
    name: 'jack',
    phone: '114',
    say() {
        return 12
    },
}

这种感觉和 extends 类似,但是它们还是有区别的。比如下面的代码,会报错!

interface Person7 {
    name: string
    say(a: number): number
}

interface Contact extends Person7 {
    phone: string
    say(): string
}

vscode 提示会提示接口不兼容的错误。

image-20221128181747317

使用 & 就不会出问题了,vscode 提示这个合并之后该方法被重载了。

// (method) say(): number (+1 overload)

type PersonDetail = Person7 & Contact
let obj1: PersonDetail = {
    name: 'jack',
    phone: '114',
    say() {
        return 12
    },
}