乐趣区

关于前端:可不要忽视了TypeScript中函数和类的重要性

上一篇文章总结了 TypeScript 的类型注解,这一篇来聊聊同样重要的函数和类

函数

以下申明了一个函数类型,通过 type 来定义类型别名,void 示意没有返回值

type fnType = () => void;
作为参数

函数能够作为参数,传递到另一个函数中

type fnType = () => void;
function foo(fn: fnType) {fn();
}
function bar() {console.log("bar");
}
foo(bar);      // bar

与 js 代码不同的点在于传入的参数须要定义类型注解

定义函数

定义函数的时候,须要给入参指定类型注解,返回值如果能够自行推导进去,也能够不写,如 add 和 minus 函数,然而作为参数时,是必须要写的,如 calc 函数中的入参 fn

function add(num1: number, num2: number): number {return num1 + num2;}
function minus(num1: number, num2: number): number {return num1 - num2;}
function calc(
  num1: number,
  num2: number,
  fn: (num1: number, num2: number) => void
) {console.log(fn(num1, num2));
}

calc(30, 20, add);      // 50
calc(30, 20, minus);      // 10
函数参数的类型

ts 中函数会规定参数的类型和个数,当个数不确定时,能够应用可选类型、残余参数、默认值

可选类型
可选类型相当于该定义的类型和 undefined 的联结类型,所以参数有三种抉择、传入该类型、不传或者 undefined

function foo(x: number, y?: number) {console.log(x, y);
}
foo(1, 2);      // 1 2
foo(3);      // 3 undefined
foo(4, undefined);      // 4 undefined

参数默认值
参数设置了默认值就使之称为了可选类型,不过有默认值的参数最好放在必传参数前面

function baz(x: number = 20, y: number) {console.log(x, y);
}
baz(10, 20);      // 10 20
baz(undefined, 20);      // 20 20

残余参数
残余参数要放在必传参数之后

function sum(num: number, ...args: number[]) {console.log(num, args);
}
sum(10);      // 10 []
sum(10, 20);      // 10 [20]
sum(10, 20, 30);      // 10 [20, 30]
this 的默认推导

在对象的办法中定义的 this,ts 是能够主动推导的,然而独立函数中的 this,是推导不进去的。

必须要在独立函数中定义 this 的类型

type ThisType = {name: string};
const eating = function (this: ThisType) {console.log(this.name + "eating~");
};

const person = {
  name: "kiki",
  eating,
};
person.eating()
函数重载

函数重载指的是函数名雷同,参数个数或者类型不同,所定义的多个解决办法。

比方须要对字符串拼接或者数字求和操作,尽管咱们晓得 + 号能够用在字符串和数字上,然而在类型检测严格的 ts 代码中,这样写编译是不通过的,须要应用【类型放大】,放大类型的判断,再进行解决。

通过联结类型,参数组合的可能性越多,须要越多的 if 语句来进行判断,并且函数的返回值类型也是未知的,在这种状况下能够应用【函数重载】

在 ts 中,定义函数名和参数类型的重载函数,再通过实现函数来定义具体实现。会依据数据类型在重载函数中调用,再执行实现函数的代码。

function add(x: number, y: number): number;
function add(x: string, y: string): string;

function add(x: any, y: any) {return x + y;}
console.log(add(1, 2));
console.log(add("a", "b"));

如果传递的参数与重载函数中定义参数不同,是无奈通过编译的。

初始化

类中定义的变量须要初始化,能够在定义类的时候就赋值或者通过 constructor 来操作

class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
const person = new Person("alice", 20);
继承

ts 和 js 中统一,都是通过 extends 实现继承,应用父级参数和办法时应用 super 关键字。

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

class Student extends Person {
  sno: number;
  constructor(name: string, age: number, sno: number) {super(name, age);
    this.sno = sno;
  }
}
const student = new Student("alice", 20, 1);
多态

应用多态能够写出更加具备通用性的代码,如果想要实现多态首先得有继承,并且父类援用指向子类对象。

class Animal {action() {console.log("animal action");
  }
}
class Dog extends Animal {action() {console.log("dog running");
  }
}
class Fish extends Animal {action() {console.log("fish swimming");
  }
}
function doAction(animals: Animal[]) {animals.forEach((animal) => {animal.action();
  });
}
doAction([new Dog()]);
doAction([new Dog(), new Fish()]);
doAction([new Dog(), new Fish(), new Animal()]);

这里相当于 const animal1: Animal = new Dog(),看起来是 Animal 对象,其实是 Dog 对象,这里的父类援用指向的是子类对象,所以最初执行的是 Dog 对象的办法

成员修饰符

成员修饰符有以下三种

  • public 示意共有的,任何中央都可见,当没有定义成员修饰符时,默认为 public
  • private 公有的,只有类中可能拜访到
  • protected 被爱护的,示意类本身和子类能够拜访到

public

class Person {
  public username: string = "alice";
  getName() {return this.username;}
}
const person = new Person();
console.log(person.username);

private
通过 private 润饰的变量,在实例对象上也是不可拜访的。

protected
通过 protected 润饰的变量,在实例对象上也是不可拜访的。

readonly

readonly 示意该属性只读,初始化了之后就不能以任何形式批改,无论是在类外部,还是批改实例对象,但当它是一个对象时,只有不扭转它的内存地址,批改对象的属性是能够的。

拜访器

拜访器给公有属性提供 get/set 办法,让咱们在类内部获取 / 批改公有属性

class Person {
  private _name: string;
  constructor(newName: string) {this._name = newName;}
  get name() {return this._name;}
  set name(newName) {if (newName) this._name = newName;
  }
}

const person = new Person("alice");
console.log(person.name);
person.name = "kiki";
console.log(person.name);
person.name = "";
console.log(person.name);

通过 get/set 属性来批改公有属性能够做到拦挡 / 判断的作用

动态成员

动态成员通过 static 关键字来定义,通过 static 定义的属性,是定义在类本身的,只能通过本人拜访,在类外部和实例对象都是无法访问到的。

抽象类

在定义很多通用的调用接口时,咱们通常会让调用者传入父类,通过多态来实现更加灵便的调用形式。

然而,父类自身可能并不需要对某些办法进行具体的实现,所以父类中定义的办法, 咱们能够定义为形象办法。

abstract class Shape {abstract getArea(): void;
}

class Circle extends Shape {
  private radius: number;
  constructor(radius: number) {super();
    this.radius = radius;
  }
  getArea() {return this.radius * this.radius * 3.14;}
}

class Rectangle extends Shape {
  private width: number;
  private height: number;
  constructor(width: number, height: number) {super();
    this.width = width;
    this.height = height;
  }
  getArea() {return this.width * this.height;}
}

function calcArea(shape: Shape) {return shape.getArea();
}

console.log(calcArea(new Circle(3)));
console.log(calcArea(new Rectangle(2, 6)));

形象办法和办法通过 abstract 来润饰,并且抽象类定义时有两条规定:

  • 形象办法必须要在子类实现
  • 抽象类是不能被实例化的

类的类型

类自身也是能够作为一种数据类型的,能够用作类型注解。

class Person {
  name: string = "alice";
  eating() {}
}
const person: Person = {
  name: "kiki",
  eating() {console.log("i am eating");
  },
};
function printPerson(person: Person) {console.log(person.name);
}

printPerson(new Person());
printPerson(person);
printPerson({name: "macsu", eating() {}});

只有合乎类的格局,就能够应用类的类型

函数和类在 JS 和 TS 中都是至关重要的,能够帮忙开发者更好标准开发时的代码,缩小线上故障~

以上就是对于 TypeScript 函数和类的内容,对于 js 和 ts,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~

退出移动版