TypeScript深入| 青训营笔记

38 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第9天

Usage

基本语法

class出现的目的,其实就是把一些相关的东西放在一起,方便管理

类中包含两个东西(也叫成员):属性方法

TS中通过class关键字可以方便的定义一个类

// 通过class创建类
class Animal {
    // 类的属性
    name: string;
    
    // 类的构造器
    constructor(name: string) {
        this.name = name;
    }
    
    // 类的方法
    sayHello():void{
        alert("hello animal:"+this.name);
    }
}
// 实例化类
var tom = new Animal("tom");
tom.sayHello();

通过new关键字可以方便的生产一个类的实例对象,这个生产对象的过程叫实例化

在实例在new出来的时候,它实际是调用了类中的一个方法进行初始化,这个方法被叫做构造器;一般都会在类中显示地写上constructor方法,如果没有显式定义的constructor,就会调用系统隐式的constructor

类的继承

我们知道JS中有继承,最开始JS是使用函数来模拟实现类的,一直到ES6出现,才开启了class以及extends 等相关关键字的使用。那为什么会有继承呢?

事实上,继承的好处在于,可以更好的重用代码,以及后期更好的维护代码

TS中的继承ES6中的类的继承极其相似,子类可以通过extends关键字继承一个类

// 通过class创建类
class Animal {
    // 类的属性
    name: string;

    // 类的构造器
    constructor(name: string) {
        this.name = name;
    }

    // 类的方法
    sayHello(): void {
        alert("hello animal:" + this.name);
    }
}

// 继承Animal
class Cat extends Animal {
    // 重写方法
    sayHello(): void {
        alert("hello cat:" + this.name);
    }
}

class Dog extends Animal {
    sayHello(): void {
        alert("hello dog:" + this.name);
    }
}


可以看见,跟ES6一样,子类构造函数必须加上super()执行父类的构造constructor函数

所以,大家常常说,一个子类实例,同时也是父类的实例

继承的格式:

class A {}
class B extends A {
  constructor() {
    super();
  }
}

如上,B继承A,那B被称为父类(超类),A被称为子类(派生类) 子类实例是可以继承父类所有的public和protected的属性和方法

除了继承,面向对象还有一个特征:多态,JS和TS中多态其实很常见,可以理解为多种状态,比如代码在运行时才能决定具体执行哪个函数

修饰符

访问修饰符

class Animal {
    private name: string; // 这里把name修饰符改为private

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

    sayHello(): void {
        alert("hello animal:" + this.name);
    }
}

class Cat extends Animal {
    sayHello(): void {
        alert("hello cat:" + this.name); //这里会报错,因为无法引用父类private修饰的属性
    }
}

class Dog extends Animal {
    sayHello(): void {
        alert("hello dog:" + this.name); //这里会报错,因为无法引用父类private修饰的属性
    }
}

  • public 允许在类的内外被调用
  • private 允许在类内被调用
  • protected 允许在类内及继承的子类中调用

当把属性的修饰符改成私有时,子类继承以后便会报错。那么如何解决呢?

使用访问修饰符的建议:尽量使用private,一般所有属性都是private,要想访问私有属性可以通过访问器

只读修饰符

readonly 只能读不能写

class Person{
  readonly name = 'Alice';
}
let p = new Person();
console.log(p.name);

需要注意的时,即使是readonly的东西,在初始化之前是可以写,即在constructor中可以初始化或更改

class Person{
  readonly name:string;
  constructor(name:string){
    this.name =  name;
  }
}

所以,我们知道了,readonly的属性,仅两个地方可以写:

  • 在声明同时赋予初始值
  • 在构造函数中赋值或者修改初始值

静态修饰符

通过static修饰的成员叫静态成员,静态成员无需实例化,直接通过类名调用

class Person{
  static a = 98;
}
console.log(person.a)

静态成员通常用于整个类所共有的一些东西

抽象类

抽象就是指不具体的,所以抽象类就是指不具体的类。所以抽象类自身没有什么功能,通常作为父类类使用

定义一个抽象类,使用abstractclass两关键字定义

abstract class A{
  abstract fn():number;
}

抽象类规定了所有继承自它的非抽象子类必须实现它的所规定的功能和相关操作,否则会报错

class B extends A{
  constructor(){
    super();
  }
  fn():number{
    return 1
  }
}

需要注意,抽象类仅仅作为基类,不能new

let b = new B();//可以
let a = new A();//报错

接口

接口是一种规范,跟抽象类有点类似

通过 interface关键字定义接口,格式如下:

interface xxx{
  属性: 类型 ;
  ...
  (函数参数) : 返回值类型
  ...
}

interface一般用于规范三个东西:

  • 函数
  • 构造器

函数interface

函数interface可规范函数的参数及返回值

interface xxx{ (参数,...):返回值类型 } 复制代码 例如

interface SearchFn {
    (key: string, page?: number): number[]
}
const search: SearchFn = function (key: string, page: number) {
    return [1, 2, 3]
}

我们可以看到,interface规范函数时,interface相当于type 当一个函数类型是接口时,要求:

参数名可不一致,但参数的值类型必须与接口定义的对应,且参数可以少不可多; 返回值必须有,且与接口定义的一致;

类interface

我们知道,继承的好处是复用代码,并且层级关系清晰。但JavaScript中继承是单继承,一个类只能有一个父类。而在TypeScript中interface会显得更加灵活,因为interface可以多实现 例如:

interface serialization {
    toJSON(): object;
    formJSON(json: object): void;
}
class Box implements serialization {
    width: number;
    height: number;
    constructor(width:number,height:number){
        this.width = width;
        this.height = height;
    }
    toJSON(): object {
        return { width: this.width, height: this.height }
    }
    formJSON(json: object): void {
        if (isSize(json)) {
            this.width = json.width;
            this.height = json.height;
        }
    }
}
function isSize(json: any): json is { width: number, height: number } {
    if (typeof json != 'object') {
        console.log('必须是object类型');
    } else {
        if (typeof json.width != 'number' || typeof json.height != 'number') {
            console.log('width 和 height 必须是number类型!!')
        }
    }
    return true;
}
let box = new Box(50,50)

如上,通过implements关键字可以让一个类实现一个接口,要求必须实现时间接口定义的方法,否则会出错

构造函数interface

构造函数interface比较特殊,是通过赋值的形式来实现,并且得跟普通interface区分开,普通interface还是使用implements。另外在接口中使用new指代构造器

interface BoxConstructorInterface{
    new (a:string)
}
interface BoxInterface{
    show(a:number):void;
}
const Box:BoxConstructorInterface = class implements BoxInterface {
    private a:string;
    constructor(a:string){
        this.a = a;
    }
    show(a:number){
        console.log(this.a)
    }
}

另外,跟类一样,interface也能继承另外一个interface 例如:

interface A { }
interface B extends A { }
class C implements B { }

所以,我们知道了,接口本身只是一种规范,里头定义了一些必须有的属性或者方法,接口可以用于规范function、class或者constructor,只是规则有点区别

模块

模块的作用

  1. 防止命名空间冲突;
  2. 将一个功能模块很容易的划分到不同文件中,更容易维护。

基本语法

module MyDemo{
    export class Animal {
        private name: string;

        get name(): string { 
            return this.name;
        }

        set name(name: string) {
            this.name = name;
        }

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

        sayHello(): void {
            alert("hello animal:" + this.name);
        }
    }

    export class Cat extends Animal {
        sayHello(): void {
            alert("hello cat:" + this.name);
        }
    }
}

12345678910111213141516171819202122232425262728

别名

module Shapes {
    export module Polygons{
        export class Square{}
        export class Triangle{}
    }
}

import polygons = Shapes.Polygons;
var sq = new polygons.Square(); //类似于 new Shapes.Polygons.Square()
var sq1 = new Shapes.Polygons.Square();

1234567891011

函数

基本语法

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

var myAdd = function(x:number,y:number):number{
    return x+y;
}