你需要知道的TypeScript(类、函数、泛型)
类的基本定义与使用
class Water {
// 声明属性
name: string
// 构造方法
constructor(name:string) {
this.name = name
}
// 方法
hello () {
return "Hello, " + this.name
}
}
// 创建类的实例
let water = new Water('water')
// 调用实例的方法
water.hello()
类的继承
class Animal {
hello(name:string) {
console.log(`Animal hello ${name}`)
}
}
class Dog extends Animal {
say() {
console.log('dog')
}
}
const dog = new Dog()
dog.say()
dog.hello('dog')
使用extends
关键字实现继承,子类中使用super
关键字来调用父类的构造函数和方法。从基类中继承了属性和方法。这里的Dog
是一个派生类,它派生自Animal
基类,通过extends
关键字。派生类通常被称作子类,基类通常被称作超类。
因为Dog
继承了Animal
的能力,因此可以创建一个Dog
的实例,该实例拥有say()
和hello()
实例方法
class Preson {
name:string
constructor(name:string){
this.name = name
}
say(hello:string='hello'){
console.log(`${this.name} and ${hello}`)
}
}
class Water extends Person {
constructor(name:string){
// 调用父类型构造方法
super(name)
}
// 重写父类型的方法
say(hello:string = 'water'){
super.say(hello)
}
}
class WaterToo extends Person {
constructor(name:string){
// 调用父类型构造方法
super(name)
}
// 重写父类型的方法
say(hello:string = 'waterToo'){
// 调用父类型方法
super.say(hello)
}
// 子类自己的方法
ower(){
console.log('ower')
}
}
const water = new Water('water')
water.say()
const waterToo = new WaterToo('waterToo')
waterToo.say()
// 父类型引用指向子类型的实例 => 多态
const newWaterToo:Person = new WaterToo('newOne')
newWaterToo.say()
// 如果子类型没有扩展自己的方法,可以让子类型引用指向父类型的实例
const otherWater:Water = new Person('otherWater')
otherWater.say()
// 如果子类有扩展自己的方法,就不能让子类引用指向父类型的实例
const errWater:WaterToo = new Person('errWater')
// err
errWater.say()
使用extends
关键字创建了Person
的两个子类:Water
和 WaterToo
,子类中拥有自己的构造函数,并且在自己的构造函数中必须调用super
来执行基类的构造函数。而且在构造函数里访问this
的属性之前,一定要先调用super
存取器getter/setter
TypeScript
支持通过getter/setter
来改变属性的赋值和读取,能让你有效的控制对对象成员的访问
class Person {
firstName: string = "a"
lastName: string = "b"
get fullName() {
return this.firstName + "-" + this.lastName
}
set fullName(value){
const names = value.split("-")
this.firstName = names[0]
this.lastName = names[1]
}
}
const p = new Person()
console.log(p.fullName)
p.firstName = "c"
p.lastName = "d"
console.log(p.fullName)
p.fullName = "eeee"
console.log(p.firstName,p.lastName)
类的静态成员
静态属性,是类对象的属性,非静态属性,是类的实例对象的属性。静态成员分为静态属性和静态方法,在变量或方法的前面加上static
关键字来定义静态属性或静态方法。
- 静态属性
class Animal {
static num = 42
constructor() {
}
}
- 静态方法
class Animal {
static isAnimal(a) {
return a instanceof Animal
}
}
访问修饰符
ts中有三种修饰符,分别是public
、private
和protected
public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
private
修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
修饰的属性或方法是受保护的,它和private
类似,区别是它在子类中也是允许被访问的
public
typeScript
中,成员默认都是public
class Water {
public name
public constructor(name) {
this.name = name
}
}
let water = new Water('water')
console.log(water.name)
water.name='a'
private
私有的,不能在声明它的类的外部访问
class Water {
private name
public constructor(name) {
this.name = name
}
}
let water = new Water("water")
console.log(water.name)
// 会报错
a.name = 'water1'
但是编译以后的代码就没有这个限制了,因为编译之后就变成了js
了。
使用private
修饰的属性或方法,在子类中也是不允许访问的:
class Person {
private name
public constructor(name){
this.name = name
}
}
// 会报错
class Water extends Person {
constructor(name) {
super(name)
console.log(this.name)
}
}
protected
protected
修饰符与private
修饰符的行为很相似,但有一点不同,protected
成员在派生类中仍然可以访问
class Person {
public name: string;
public constructor(name: string) {
this.name = name;
}
public jump(height: number = 0) {
console.log(`${this.name} jump ${height}m`);
}
}
class Water extends Person {
private age: number = 18;
protected sex: string = "男";
jump(height: number = 5) {
console.log("Water jumping...");
super.jump(height);
}
}
class Student extends Person {
jump(height: number = 6) {
console.log("Student jumping...");
console.log(this.sex); // 子类能看到父类中受保护的成员
// console.log(this.age) // 子类看不到父类中私有的成员
super.jump(distance);
}
}
console.log(new Person("abc").name); // 公开的可见
// console.log(new Person('abc').sex) // 受保护的不可见
// console.log(new Person('abc').age) // 私有的不可见
readonly修饰符
readonly
关键字将属性设置为只读的,只读属性必需在声明时候或构造函数里被初始化
class Person {
readonly name: string = "abc";
constructor(name: string) {
this.name = name;
}
}
let water = new Person("water");
// water.name = 'peter' // error
参数属性
在上面的例子中,Person
类里定义一个只读成员name
和一个参数为name
的构造函数,并且立刻将name
的值赋给this.name
,这种情况经常会遇到,参数属性可以方便的让在一个地方定义并初始化一个成员。
class Person {
constructor(readonly name:string){}
}
const water = new Person('water')
仅在构造函数里使用 readonly name: string
参数来创建和初始化 name
成员。 我们把声明和赋值合并至一处。 参数属性通过给构造函数参数前面添加一个访问限定符来声明。使用 private
限定一个参数属性会声明并初始化一个私有成员;对于 public
和 protected
来说也是一样的
抽象类
抽象类作为其他派生类的基类使用。abstract
关键字用于定义抽象类和在抽象类内部定义抽象方法
什么是抽象类
- 抽象类不允许被实例化
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Dog');
// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.
- 抽象类中的抽象方法必须被子类实现
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public eat() {
console.log(`${this.name} is eating.`);
}
}
let cat = new Cat('Tom');
// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.
我们定义了一个类 Cat
继承了抽象类 Animal
,但是没有实现抽象方法 sayHi
,所以编译报错了。 下面是一个正确使用抽象类的例子:
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`);
}
}
let cat = new Cat('Tom');
因为实现了抽象方法sayHi
,所以编译通过了。
为函数定义类型
在js
中有两种常见的定义函数的方式(函数声明、函数表达式)
// 函数声明
function sum(x,y){
return x + y
}
// 函数表达式
const mySum = function(x,y) {
return x + y
}
ts中的函数声明
function sum(x:number,y:number):number {
return x + y
}
定义类型之后,如果输入的参数不符合定义时候的类型和数量,这样编译是不会通过的
function sum(x: number, y: number): number {
return x + y;
}
sum(1, 2, 3);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
return x + y;
}
sum(1);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
函数表达式
const mySum = function (x:number,y:number):number{
return x + y
}
其实上面例子可以编译通过其实还涉及到了类型推论,因为只定义了右侧的类型,左侧的是靠推断出来的,所以完整的定义如下
const mySum:(x:number,y:number) => number = function(x:number,y:number):number{
return x + y
}
用接口定义函数类型
interface SearchFunc {
(name:string,age:string):string
}
let mySearch:SearchFunc
mySearch = function(name:string,age:string){
return name + age
}
可选参数和默认参数
function FullName(firstName:string,lastName?:string):string{
if(lastName){
return firstName + '-' + lastName
}else {
return firstName
}
}
这里有个需要注意的地方,可选参数必须接在必需参数后面,如果不准守这个规则会导致编译不通过
// 编译会报错
function FullName(firstName?:string,lastName:string){
if(lastName){
return firstName + '-' + lastName
}else {
return firstName
}
}
可以为参数添加默认值
function FullName(firstName:string='a',lastName?:string):string {
if(lastName){
return firstName + '-' + lastName
}else {
return firstName
}
}
剩余参数
可以把剩余的参数全部集中在一个变量中
function info(x:string,...args:string[]){
console.log(x,args)
}
info('abc','a','b','c')
函数重载
所谓函数重载,即函数名相同, 而形成不同的多个同名函数。在 JavaScript
里, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的。但在 TypeScript
里, 由于有了类型系统,所以就有了函数重载。
/*
* 函数重载: 函数名相同, 而形参不同的多个函数
* 需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也
* 可以接收2个number类型的参数进行相加
*/
// 重载函数声明
function add(x: string, y: string): string;
function add(x: number, y: number): number;
// 定义函数实现
function add(x: string | number, y: string | number): string | number {
// 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
if (typeof x === "string" && typeof y === "string") {
return x + y;
} else if (typeof x === "number" && typeof y === "number") {
return x + y;
}
}
console.log(add(1, 2));
console.log(add("a", "b"));
// console.log(add(1, 'a')) // error
范型
使用函数范型
function createArray<T>(value:T,count:number) {
const arr: Array<T> = []
for (let index = 0;index < count;index++){
arr.push(value)
}
return arr
}
const arr = createArray<number>(11,3)
const arr1 = createArray<string>("aa",3)
多个泛型参数的函数
function func<K,V>(a:K,b:V):[K,V]{
return [a,b]
}
const res = func<string,number>('a',1)
泛型接口
interface IBase<T> {
data:T[]
add: (t:T) => void
get: (id:number) => T
}
class User {
id?:number
name:string
age:number
constructor(name,age){
this.name = name
this.age = age
}
}
class UserWater implements IBase<User> {
data:User[] = []
add(user: User):void {
user = {...user,id:Date.now()}
this.data.push(user)
}
get(id:number):Uset {
return this.data.find((item) => item.id === id)
}
}
泛型类
在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
class Water<T>{
value: T
add: (x:T,y:T) => T
}
let water = new Water<number>()
water.value = 1
water.add = function(x,y){
return x + y
}
泛型约束
没有泛型约束
function fn<T>(x:T):void {
// 不是所有的类型都有length属性
console.log(x.length) // error
}
使用泛型约束解决
interface Water {
length: number
}
// 指定泛型约束
function func<T extends Water>(x:T):void {
console.log(x.length)
}