TypeScript重要知识点(二)[函数、类、接口、接口约束类]

148 阅读8分钟

Ts 函数

与 Js 对比

TSJS
含有类型无类型
箭头函数箭头函数(ES2015)
函数类型无函数类型
必填和可选参数所有参数都是可选的
默认参数默认参数
剩余参数剩余参数
函数重载无函数重载

参数类型和返回类型

function createUserId(name: string, id: number): string {
  return name + id;
}

const userId: string = createUserId("John", 123); 
console.log(userId); // 输出 "John123"

createUserId 函数接收两个参数:nameid,分别为字符串类型和数字类型。函数的返回类型为字符串类型。

函数的功能是将nameid拼接在一起,并返回拼接后的结果。

可选参数

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

问题一、可选参数未定义(undefined)

function createUserId(name: string, id: number, age?: number): string {
  return name + id + age ;
}
const userId: string = createUserId("John", 123); 
console.log(userId); // 输出 "John123undefined"
const userId2: string = createUserId("John", 123, '456'); 
console.log(userId2); // 输出 "John123456"

function add(x:number, y:number, z?:number):number {
  const c = y + z
    return x + c
}
const a = add(1,2,3)
const b = add(1,2)
console.log(a); // 6
console.log(b); // NaN

结果:

image.png

解决方法:在使用z之前,先进行判断是否为undefined,如果是则给z一个默认值

function add(x: number, y: number, z?: number): number {
  const c = y + (z !== undefined ? z : 0);
  return x + c;
}

解决方法结果:

image.png

问题二、必选参数不能跟在可选参数后面(可选参数必须放在函数入参的最后面),不然会导致编译错误

function add(x:number, y?:number, z:number):number {
  const c = z + (y !== undefined ? y : 0);
    return x + c
}

const a = add(1,2,3)
console.log(a); // 6

结果:

image.png

解决方法:将可选参数写在必选参数后面

问题三、参数不能同时为可选参数和默认参数(问号?和初始化器 =1

function add(x:number, y:number, z?:number = 1):number {
  const c = y + (z !== undefined ? z : 0);
    return x + c
}

const a = add(1,2,3)
console.log(a);

结果:

image.png

默认参数

在定义参数时,给参数一个默认值

function add(x:number, y:number = 100):number {
    return x + y
}

add(100)  // 200

剩余参数

function push(array, ...items) {
  items.forEach(function (item) {
    array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);
console.log(a); // 输出 [1, 2, 3]

函数重载

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。

在 TS 中,实现函数重载,需要多次声明这个函数,前几次是函数定义,列出所有的情况,最后一次是函数实现

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

还有和函数重载差不多的方法重载

方法重载是指在同一个类中方法同名,参数不同(参数类型不同、参数个数不同或参数个数相同时参数的先后顺序不同),调用时根据实参的形式,选择与它匹配的方法执行操作的一种技术。

类中成员方法满足重载的条件是:在同一个类中,方法名相同且参数列表不同。

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: string, b: number): string;
  add(a: number, b: string): string;
  add(a: Combinable, b: Combinable) {
    if (typeof a === "string" || typeof b === "string") {
      return a.toString() + b.toString();
    }
    return a + b;
  }
}

const calculator = new Calculator();
const result = calculator.add("cccc", " vvvv");

写起来挺麻烦的,后面了解泛型之后写起来会简洁一些,不必太纠结函数重载,知道有这个概念即可,平时一般用泛型来解决类似问题。

Ts 接口

基本概念

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

定义 interface 一般首字母大写

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

let Semlinker: Person = {
  name: "userName",
  age: 33
};

注意一、少写了属性,报错

image.png 注意二、多写了属性,报错

image.png

可选属性/只读属性

可选属性:跟函数的可选参数是类似的,在属性上加个 ?

只读属性:改变只读属性的值时,会报错

interface Person {
  readonly name: string;
  age?: number;
}

ReadonlyArray<T> 类型:表示整个数组,都不能被修改,和创建数组的 Array<T> 相似

let a: number[] = [1, 2, 3, 4];
let ArrData: ReadonlyArray<number> = a;
ArrData[0] = 12; 
ArrData.push(5); 
ArrData.length = 100; 
a = ArrData; 

结果:

image.png

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

解决一个对象上有多个不确定的属性

interface LikeArray {
    [propName: number]: string
}

const arr: LikeArray = [456, 789]
console.log(arr[0])

结果:

image.png

报错分析:

因为在定义接口LikeArray时,指定了索引签名的类型为string,但是在实际使用时,将一个包含数字的数组赋值给了arr变量。

LikeArray只能通过索引访问字符串类型的值。

解决方法

// 方法一、将接口`LikeArray`的索引签名类型改为适合实际情况的类型
interface LikeArray {
  [propName: number]: number;
}
const arr: LikeArray = [456, 789];
console.log(arr[0]); // 输出 456

// 方法二、将`arr`的值改为字符串类型的数组
interface LikeArray {
  [propName: number]: string;
}
const arr: LikeArray = ['456', '789'];
console.log(arr[0]); // 输出 "456"

Ts 类

在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。

类是基础

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

class Person {
    name: string
    // 构造函数,接受一个参数 `name` ,并将其赋值给实例的 `name` 属性
    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()
console.log(s1.study() ) // 输出 "lin needs study"

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

image.png

继承---super关键字

super关键字是用来继承,父类的属性

如果子类没有自己的属性,可以不写super;如果子类有自己的属性,就要用super关键字来讲父类的属性继承过来。

class Student extends Person {
    grade: number
    constructor(name: string,grade:number) {
        super(name)
        this.grade = grade
    }
}
const s1 = new Student('lin', 100)

console.log(s1.name) // 'lin'
console.log(s1.grade) // 100

不写super关键字就会报错

image.png

多态

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

多态指的是,父类定义一个抽象方法,在多个子类中有不同的实现,运行的时候不同的子类就对应不同的操作

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

class Student extends Person {
    speak() {
        console.log(`${this.name} is speaking`)
    }
}
const s0 = new Person('shan')
console.log(s0.speak()) // "shan is speaking"

const s1 = new Student('long')
console.log(s1.speak()) // "long is speaking"

public、private、protecte

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

  • public:公有的,一个类里默认所有的方法和属性都是 public。
class Person {
    public name: string
    public constructor(name: string) {
        this.name = name
    }
    public speak() {
        console.log(`${this.name} is speaking`)
    }
}

  **上下写法,效果一样**

class Person {
    name: string
    constructor(name: string) {
        this.name = name
    }
    speak() {
        console.log(`${this.name} is speaking`)
    }
}
  • private:私有的,只属于这个类自己,它的实例和继承的子类都访问不到。
class Person {
    private name: string
    public constructor(name: string) {
        this.name = name
    }
    public speak() {
        console.log(`${this.name} is speaking`)
    }
}

实例访问 name 属性和继承的子类访问 name 属性,都会报错

image.png

  • protected 受保护的,继承它的子类可以访问,实例不能访问。
class Person {
    protected name: string
    public constructor(name: string) {
        this.name = name
    }
    public speak() {
        console.log(`${this.name} is speaking`)
    }
}

子类可以访问,实例访问报错

image.png

static

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

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

const s1 = new Person('ting')

// s1.pi

class Student extends Person {
    speak() {
        return Person.pi *2
    }
}
const s2 = new Student('long')
console.log(s2.speak()) // 6.28

this 类型

类的成员方法可以直接返回一个 this,这样就可以很方便地实现链式调用。

class StudyStep {
  step1() {
    console.log('listen')
    return this
  }
  step2() {
    console.log('write')
    return this
  }
}

const s = new StudyStep()

console.log(s.step1().step2() )    // 两个方法都可以调用

子类用 this类型 可以调用父类的方法

class StudyStep {
  step1() {
    console.log('listen')
    return this
  }
  step2() {
    console.log('write')
    return this
  }
}

class MyStudyStep extends StudyStep {
  next() {
    console.log('before done, study next!')
    return this   
  }
}

const m = new MyStudyStep()

console.log(m.step1().next().step2().next())  // 父类型和子类型上的方法都可随意调用

抽象类

抽象类是一个广泛和抽象的概念,不是一个实体

比如说:动物这个概念是很广泛的,猫、狗、狮子都是动物,但动物却不好是一个实例,实例只能是猫、狗或者狮子。

抽象类有两个特点:

  • 抽象类不允许被实例化
  • 抽象类中的抽象方法必须被子类实现

抽象类用一个 abstract 关键字来定义。

  • 抽象类不允许被实例化
abstract class Animal {}

const test = new Animal()

结果:

image.png

  • 抽象类中的抽象方法必须被子类实现 (子类要实现抽象类的所有方法,少一个都会报错)
abstract class Animal {
    constructor(name:string) {
        this.name = name
    }
    public name: string
    public abstract sayHi():void
    public abstract sayHi1():void
}

class Dog extends Animal {
    constructor(name:string) {
        super(name)
    }
    public sayHi() {
        console.log('wang')
    }
}

image.png

接口(interface)约束 类(class)

implements(实现)

class 实现 interface

// 定义一个接口
interface MusicInterface {
    playMusic(): void
}

// 在 Cellphone类 中实现 MusicInterface接口
class Cellphone implements MusicInterface {
    playMusic() {}
}

注意:class 必须要满足接口上的所有条件(如果接口中有两个方法,类只实现了一个,就会报错)

处理公共的属性和方法

多个类有相同的方法,如果用继承来获得这些方法太繁琐,用接口来获得比较简单

interface MusicInterface {
    playMusic(): void
}

class Car implements MusicInterface {
    playMusic() {}
}

class Cellphone implements MusicInterface {
    playMusic() {}
}

甚至于一个类,还可以实现多个接口的方法

interface MusicInterface {
    playMusic(): void
}

interface CallInterface {
    makePhoneCall(): void
}

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

其他Ts基础文章

Ts基础知识点(一)Ts与Js区别、Ts基础类型、Ts断言