TypeScript学习笔记—类

3,032 阅读6分钟

何为类

es6之前:没有这个概念,当时听到最多的是原型对象以及原型链;
es6之后:简单理解为类 === class
Java的里面的解释:类是一个模板,它描述一类对象的行为和状态

简单理解为,是一张图纸,它描述了一种东西,我们可以根据图纸来把这个东西做出来;假设我们要做一个杯子,那么就是这个杯子的图纸。我们制作杯子的这个过程叫做类的实例化;因此我们可以得知,接口一样,用来描述一类东西;

前面我们说过,TypeScript是JavaScript的超集,所以ts的语法和js是一样的;在js里,我们用class来创建一个类,同样,在ts里,我们也可以用class来创建类。

类与接口的区别

前面我们说了接口一样,都是用来描述一种东西需要的特性,那么它们有什么区别吗?
就像高r和高尔夫,高配和低配的区别;

interface Human {
    name: string;
    speak(something: string): void;
}
class Human {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    speak(something: string): void {
        console.log(something);
    }
}

假设我们现在有如上的代码,此时我们想声明一个对象或者变量,它的类型是上面的一个,我们应该怎么写?

// interface
let man: Human = {
    name: 'X-man',
    speak(something) {
        console.log(something);
    }
}
// class
let man2 = new Human('T-man');

发现了吗?
高配的虽然写起来比较麻烦,但是实例化的时候很简单。

那么问题来了!

语法

声明类

使用关键字class

class Something {}

调用类

同js语法

class Something {}

let a = new Something();

constructor

不要忘了在你的类里写上constructor

class A {
    constructor(/** 这里是实例化类的时候需要传递的参数 **/) {
        /** 这里可以初始化参数 **/
    }
}

类的属性

在你创建一个类的时候,你以为你写的所有属性都是类的属性,其实它们都是实例化对象的属性。如果想要添加类的属性,需要在属性前加上关键字static

class A {
    static num: number = 1;    
    str: string = 'hello';
    
    static say(something: string): void {
        console.log(something)
    }
}
let a = new A();

a.str // 'hello'
a.num // Property 'num' is a static member of type 'A'
a.say // Property 'say' is a static member of type 'A'

this

在js里人人都会遇到的问题,以及人人都知道的问题。在写某一个地方的时候,此时,劳资的this到底tm的是个啥??

在这里,this代指的是你在写的类。你写在类里的方法或者属性都可以通过this来调用;

class A {
    b: string;
    constructor() {
        console.log(this)
        this.b = 'b';
    }
}
let a = new A(); // class A

继承

同样,继承和js里类的继承语法是一样的,通过子类 extends 父类来进行继承;

class Words {
    a: string;
}
class EnglishWords extends Words {
    b: string;
}

let english = new EnglishWords();

同样记得,如果在子类里声明了constructor,别忘了在constructor里起始位置调用super()

class Words {
    a: string;
}
class EnglishWords extends Words {
    b: string;
    construct() {
        super(); // 不声明会报错:Constructors for derived classes must contain a 'super' call.
    }
}

let english = new EnglishWords();

修饰符

实际上修饰符有三个,分别是:publicprivateprotected。但是ts里默认类里面所有的属性都是public,所以一般情况下我们可以直接忽略public

private

直译为:私人的,私有;

我们可以理解为,一旦你给类里面的属性添加了private修饰符,那么这个属性就是一个私有属性,只能在当前声明类的内部使用;相当于给当前类添加了一个单独的作用域;

class A {
    a: number = 1;
    private b: number = 2;
}
class B extends A {
    c: number = 3;
}
let b = new B();
b.b // Property 'b' is private and only accessible within class 'A'.

所谓的私有即是:只有我可以用,别人谁都不可以;

class A {
    a: number = 1;
    private b: number = 2;
}
class B extends A {
    c: number = 3;
    constructor() {
        super(); // 不写会报错
        this.b // Property 'b' is private and only accessible within class 'A'.
    }
}

protected

直译为:保护,防护,保卫……

其实逻辑上来说,这个修饰符的命名有些问题。因为实际上他的作用和他的名字没有半毛钱关系;

要理解这个修饰符的话,我们需要对比private来看。

一个属性如果有private修饰符的话,那么他只能在创建它的类中使用;如果改成protected的话,那么创建类以及所有的子类中都可以使用。(实例化对象仍然无法访问)

class A {
    a: number = 1;
    protected b: number = 2;
}
class B extends A {
    c: number = 3;
    constructor() {
        super(); // 不写会报错
        this.b // 不会报错
    }
}
let b = new B();
b.b // Property 'b' is protected and only accessible within class 'A' and its subclasses.

存取器

我们来看这样一段代码:

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

const Li = new Human('Li', 20);

没有任何问题! 是吧!

假如这个时候出现了一个智者,他把你对象的属性给你改了!

Li.age = -1;

你能怎么办???

所谓的存取器其实最大的作用就是,把真正的屎隐藏起来,展示给使用者优美的代码就好了。方便控制数据,不会出现以外的问题。

class Human {
    _name: string;
    get name(): string {
        return this._name;
    }
    _age: number;
    get age(): number {
        return this._age
    }
    set age(val: number): void {
        if (val > 0) this._age = val; 
        else this._age = 0;
    }
    constructor(name: string, age: number) {
        this._name = name;
        this._age = age;
    }
}

const Li = new Human('Li', 20);

Li.name // 'Li'
Li.age // 20
Li.age = 21
Li.age // 21
Li.age = -1
Li.age // 0

注意: 在使用存取器之后,原本的类里面会增加对应属性。在上面的例子里,Human类在实例化之后会有四个属性,分别是:_age, age, _name, name。如果不想展示内部数据的话,在对应数据之前加上private修饰符。

class Human {
    private _name: string;
    get name(): string {
        return this._name;
    }
    private _age: number;
    get age(): number {
        return this._age
    }
    set age(val: number): void {
        if (val > 0) this._age = val; 
        else this._age = 0;
    }
    constructor(name: string, age: number) {
        this._name = name;
        this._age = age;
    }
}

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。(作者:看不懂)

简化:抽象类做为其它派生类的基类使用 = 父类;它们一般不会直接被实例化 = 不会被实例化。
结论:不会被实例化的父类。

interface A {
    a: number;
    b(x: string): void;
}

这是一个接口,我们通过接口定义了一个类型A。仔细观察,你会发现我们只定义了它的组成,并没有定义具体的实现。

class A {
    a: number;
    b(x: string): void;
}

同样的,接口也可这样定义。但是如果只是这样定义的话,ts会提示我们Function implementation is missing or not immediately following the declaration.,不可以声明未实现的函数。我们可以在不想写未实现的函数前面加上abstract修饰符,同时也需要在函数前加上abstract。因为abstract只能出现在abstract函数内。

abstract class A {
    a: number;
    abstract b(x: string): void;
}

注意:抽象类不能实例化,必须要由子类继承。

// 报错
abstract class A {
    a: number;
    abstract b(x: string): void;
}
let a = new A(); // Cannot create an instance of an abstract class.

// 正确
abstract class A {
    a: number;
    abstract b(x: string): void;
}
class B extends A {
    b(x: string) {
        console.log(x)
    }
}
let a = new B();