likes
comments
collection
share

TypeScript 中的class类和类型兼容性 —— java:?

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

ts 高级类型

  1. class 类(🫵)
  2. 类型兼容性(🫵)
  3. 交叉类型(🐦)
  4. 泛型 和 keyof ???(🐦)
  5. 索引签名类型 和 索引查询类型 ???(🐦)
  6. 映射类型 ???(🐦)

接到新需求了,代码库看不懂一点,完全不会,跑了半天的代码跑不动才发现要12版本的node才能跑,写不动了~

🐦🐦🐦🐦······

1. class 类

1.1 基本使用

ts全面支持ES6中引入的class关键字,并为其添加了类型注解和其他语法(比如可见性修饰符等)

示例:

class Person{
    age:number
    sex = '男'
    // 相当于 sex:string = '男'
    // ts可以自动判断省略类型注解
}
const p = new Person()

TypeScript 中的class类和类型兼容性 —— java:?

特点:

  1. 根据 TS 中的类型推论,可以知道Person类的实例对象p的类型是Person
  2. TS 中的class,不仅提供了class的语法功能,也作为一种类型存在。

1.2 构造函数和方法

示例:

class Person {
    age: number = 18
    sex: string = '男'
    height: number = 170

    constructor(age: number, sex: string, height: number){
        this.age = age
        this.sex = sex
        this.height = height
    }

    say(n:number){
        this.height += n
        console.log(`我今天又长高了${n}cm`)
    }
}

const p = new Person(18, '男', 180)
p.say(2) // 我今天又长高了2cm

特点:

  1. 成员初始化(比如age:number)后,才可以通过this.age来访问实例成员。
  2. 需要为构造函数的参数指定类型注解,否则会被隐式推断为any
  3. 构造函数不需要返回值类型,不能写类型注解,因为它没有返回值
  4. 方法与之前函数类型用法一致(参数和返回值)

TypeScript 中的class类和类型兼容性 —— java:?

TypeScript 中的class类和类型兼容性 —— java:?

1.3 继承

class继承的两种方式:

  1. extends(继承父类)
  2. implements(实现接口)

说明:js中只有extends,而implements是ts提供的。

ps:纯java面向对象~

extends:

class animal {
    name: string = 'animal'
    say(say: string){
        console.log(say)
    }
}

class dog extends animal {
    name: string = 'dog'
    eat(){
        console.log('吃狗粮')
    }
}

const d = new dog()
// 从anmial类中继承的方法,子类和父类都有
d.say('汪汪汪') // 汪汪汪
// dog类特有的方法,父类animal中没有
d.eat() // 吃狗粮

注意:继承是指子类可以从父类获取到没有的属性或者方法,从而使得自己能够拥有这些来进行使用和重写。

implements:

// 定义一个接口
interface IAnimal {
    name: string;
    age: number;
    eat(): void;
}

// 定义一个类,实现接口
class Cat implements IAnimal {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    eat() {
        console.log(this.name + " is eating.");
    }
}

let cat = new Cat('Tom', 3);
cat.eat();  // 输出:Tom is eating.

注意:实现接口后,子类必须按照接口中的属性和方法来进行设计,否则ts会提示报错。

TypeScript 中的class类和类型兼容性 —— java:?

1.4 可见性修饰符

可以使用TS来控制class的方法或属性对于class外的代码是否可见。

可见性修饰符包括:

  1. public(公有的)
  2. protected(受保护的)
  3. private(私有的)

public:表示公有的、公开的,公有成员可以被任何地方访问,默认可见性,可以直接省略。

class Animal {
    public eat() {
        console.log('好吃,有wer!')
    }

    say(n: string) { // 默认为publish,可以省略
        console.log(`what can i say! ${n}!`)
    }
}

const cat = new Animal()
cat.eat() // 好吃,有wer!
cat.say('man') // what can i say! man!

protected:表示受保护的,仅对其声明所在类和子类中(非实例对象)可见。在子类的方法内部可以通过this来访问父类中受保护的成员,但是,对实例不可见!

class Animal {
    protected eat() {
        console.log('好吃,有wer!')
    }

    say(n: string) { // 默认为publish,可以省略
        console.log(`what can i say! ${n}!`)
    }
}

class Cat extends Animal {
     newsay(n: string) {
        this.eat()
        console.log(`what can i say! ${n}!`)
    }
}

const cat = new Cat()
cat.eat() // 报错,子类可以访问属性,但是实例对象无法访问
cat.newsay('man') // 好吃,有wer! what can i say! man!

TypeScript 中的class类和类型兼容性 —— java:?

protected:表示私有的,只在当前类中可见,对实例对象以及子类也会不可见的。私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的。

class Animal {
    private eat() { // 私有属性,只能在本类中访问
        console.log('好吃,有wer!')
    }

    say(n: string) { // 默认为publish,可以省略
        console.log(`what can i say! ${n}!`)
    }
    protected run() { // 受保护的方法,可以在子类访问
        this.eat()    // 本类访问
    }
}

class Cat extends Animal {
     newsay(n: string) { // 子类自己的方法
        this.run()       // 受保护的方法,可以在子类访问
        console.log(`what can i say! ${n}!`)
    }
}

const cat = new Cat()
cat.newsay('man') // 好吃,有wer! what can i say! man!
cat.eat() // 报错,属性“eat”为私有属性,只能在类“Animal”中访问。

TypeScript 中的class类和类型兼容性 —— java:?

题外话:除了可见性修饰符之外,还有一个常见修饰符就是:readonly(只读修饰符)

readonly

  1. 表示只读,用来防止在构造函数之外对属性进行赋值,只能修饰属性不能修饰方法。
  2. readonly修饰的属性若没有添加类型注解则会判断为字面量类型,建议提供明确的类型。
  3. 接口或者{}表示的对象类型,也可以使用readonly。

1.属性是只读,只能修饰属性不能修饰方法

TypeScript 中的class类和类型兼容性 —— java:?

TypeScript 中的class类和类型兼容性 —— java:?

2.readonly修饰的属性若没有添加类型注解则会判断为字面量类型,建议提供明确的类型。

TypeScript 中的class类和类型兼容性 —— java:?

TypeScript 中的class类和类型兼容性 —— java:?

3.接口或者{}表示的对象类型,也可以使用readonly。

TypeScript 中的class类和类型兼容性 —— java:?

TypeScript 中的class类和类型兼容性 —— java:?

2. 类型兼容性

2.1 结构化类型系统

在编程语言中,类型系统的设计主要分为:

  1. StructuralTypesystem (结构化类型系统)
  2. NominalType System (标明类型系统)

ts采用的是结构化类型系统,也叫做ducktyping(鸭子类型),类型检查关注的是值所具有的“形状”。在结构类型系统中,如果两个对象具有相同的“形状”,则认为它们属于同一类型。对于对象类型来说,成员多的类型可以兼容成员少的类型。

示例:

class Cat {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Dog {
    name: string;
    age: number;
    type: string;

    constructor(name: string, age: number, type: string) {
        this.name = name;
        this.age = age;
        this.type = type;
    }
}

const animal: Cat = new Dog("Rex", 5, "Husky");
const otheranimal: Dog = new Cat("Tom", 3); // 报错 类型 "Cat" 中缺少属性 "type",但类型 "Dog" 中需要该属性。
console.log(animal); // 输出 Dog { name: 'Rex', age: 5, type: 'Husky' }

解释:

因为TS是结构化类型系统,只检查Cat和Dog的结构是否相同或兼容(兼容,都具有name和age两个属性,属性类型也相同,即Dog可以兼容Cat)。但是如果在标明类型系统中(比如,C#、java等),它们是不同的类,类型无法兼容。

2.2 接口兼容性

除class之外,ts中其他类型也存在相互兼容的情况,包括接口兼容性和函数兼容性等。

接口之间的兼容性,类似于class,且class和interface之间也可以兼容。

interface Cat {
    name: string;
    age: number;
}

interface Dog {
    name: string;
    age: number;
    type: string;
}

let a1: Cat = {
    name: 'cat',
    age: 3,
}

let a2: Dog = {
    name: 'dog',
    age: 3,
    type: 'dog',
}

console.log(a1); // 输出 { name: 'cat', age: 3 }

a2 = a1; // 报错,类型 "Cat" 中缺少属性 "type",但类型 "Dog" 中需要该属性。
a1 = a2;

console.log(a1); // 输出 { name: 'dog', age: 3, type: 'dog' }
console.log(a2); // 输出 { name: 'dog', age: 3, type: 'dog' }

2.3 函数兼容性

函数之间兼容性比较复杂,需要考虑参数个数、参数类型和返回值类型。

1.参数个数:参数多的兼容参数少的(参数少的可以赋值给多的)

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

let f1: F1 = (n: number) => {
    console.log(n)
}

f1(1); // 1
// 参数少的兼容参数多的,好比foreach方法我只接受一个value参数,但是index我也可以接受
let f2: F2 = f1
f2(1, 2); // 1

const arr = [1,2,3]
arr.forEach(() => {})
arr.forEach((value) => {})
arr.forEach((value, index) => {})
arr.forEach((value, index, array) => {})

特点:

  1. 参数少的可以赋值给参数多的,所以f1可以赋值给f2。
  2. 在js中省略用不到的函数参数实际上是很常见的,例如foreach方法,这样的使用方式促成了TS中函数类型之间的兼容性。
  3. forEach 方法的第一个参数是回调函数,类型为(value:string, index: number, array; string) => void,因为回调函数是有类型的,所以TS会自动推导出参数item、index、array的类型。

注意:

函数兼容性与前面所讲的接口兼容性并不一致!接口兼容性是属性多的可以兼容属性少的,而函数兼容性是参数少的可以赋值给参数多的!

可以这么理解,接口的属性兼容一定要包含必要有属性,所以接口兼容性是属性多去兼容属性少的,这样可以保证属性一定具有;而函数兼容性就好比 forEach 方法,参数具有可选性,所以是参数少的可以赋值给参数多的。(个人总结理解,比较抽象~

2.参数类型:相同位置的参数类型要相同(原始类型)或兼容(引用类型)

TypeScript 中的class类和类型兼容性 —— java:?

3.返回值类型:只关注返回值类型本身就行。

TypeScript 中的class类和类型兼容性 —— java:?

ps: 如果有学过java的uu看到这会发现真的很绕,类似继承和多态,但又有哪说不出来的不一样,感觉ts又想往java等这种强语言上靠,又抛不开自己是js儿子的事实。但是js依旧是弱语言,ts只是帮助代码增加可读性,就算ts是“错”的,依旧不会影响js的运行,而强类型语言是运行不下去的(纯属个人猜测~

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