欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

TS教程 - 详解4类别、类与接口、泛型概念

最编程 2024-07-27 20:57:28
...

2019-10-29:

学习内容:类、类与接口、泛型

补充:

1、ts中interface与class的区别:

interface:接口只声明成员方法,不做实现。

class:类声明并实现方法。

也就是说:interface只是定义了这个接口会有什么,但是没有告诉你具体是什么。

 

2.extends 与 implement的区别:

(1)extends是继承父类,只要那个类不是声明为final或者那个类定义为abstract的就能继承。

(2)java中不支持多重继承,但是可以用接口来实现,这样就要用到implements,继承只能继承一个类,但implements可以实现多个接口,用逗号分开就行了

 class A extends B implements C,D,E

implements就是实现的意思, 顾名思义它实现一个已经定义好的接口中的方法

 


  

一、类(ES6,ES7,TS类的区别):

传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 class

TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。

 

(1)类的概念: What is class?

 

<-- 知识回顾:ES6中类的用法 -->

属性和方法:

使用 class定义类,使用 constructor定义构造函数

通过 new生成新实例的时候,会自动调用构造函数

class Animal {
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    } // 方法
}

let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

类的继承

使用 extends关键字实现继承,子类中使用 super关键字来调用父类的构造函数和方法。

class Cat extends Animal {
    constructor(name) {
        super(name); // 调用父类的 constructor(name)
        console.log(this.name);
    }
    sayHi() {
        return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
    }
}

let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom

 

静态方法

使用 static修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用

class Animal {
    static isAnimal(a) {
        return a instanceof Animal;
    }
}

let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function

 

 

<-- ES7中类的用法 -->

ES7 中有一些关于类的提案,TypeScript 也实现了它们。

实例属性:

ES6 中实例的属性只能通过constroctor中的 this.xxx来定义,ES7 提案中可以直接在类里面定义:

class Animal {
    name = 'Jack';

    constructor() {
        // ...
    }
}

let a = new Animal();
console.log(a.name); // Jack

静态属性:

ES7 提案中,可以使用 static定义一个静态属性

class Animal {
    static num = 42;

    constructor() {
        // ...
    }
}

console.log(Animal.num); // 42

 

<-- TS中类的用法 -->

(1)公共、私有与受保护的修饰符:

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 publicprivate和 protected这些编译到js是一样的,也就是说js不设置这三者

  • public修饰的属性或方法是公有的,可以在任何地方被访问到默认所有的属性和方法都是 public

  • private修饰的属性或方法是私有的,不能在声明它的类的外部访问

  • protected修饰的属性或方法是受保护的,它和 private类似,区别是它在子类中也是允许被访问的


(1)当一个成员被标记成private时,它不能在声明它的类的外部访问(在子类和类的实现的对象中都不能访问。在子类可以通过调用使用这个属性的方法来间接来使用这个属性。):

// 实例访问类成员属于外部访问

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // 错误: 'name' 是私有的.

TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。

然而,当我们比较带有 private或 protected成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected成员也使用这个规则。

 

(2)protected和private类似,但是protected成员在派生类中仍然可以访问(在子类和类的实现的对象中允许访问),仅针对对象。

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

class Employee extends Person {
    private department: string;

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

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误

我们不能在 Person类外使用 name,但是我们仍然可以通过 Employee类的实例方法访问,因为 Employee是由 Person派生而来的。

当构造函数修饰为 private时,该类不允许被继承或者实例化;当构造函数修饰为 protected时,该类只允许被继承

 

例子2: 修饰符还可以使用在构造函数参数中,等同于类中定义该属性,使代码更简洁。

class Animal {
    // public name: string;
    public constructor (public name) {
        this.name = name;
    }
}

 

(2)readonly

只读属性关键字,只允许出现在属性声明或索引签名中。

class Animal {
    readonly name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom'; // Error 

// index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.

 

注意:如果 readonly和其他访问修饰符同时存在的话,需要写在其后面。写在构造函数中定义该属性

class Animal {
    // public readonly name;
    public constructor(public readonly name) {
        this.name = name;
    }
}

 

(3)抽象类:

abstract用于定义抽象类和其中的抽象方法。

-- 什么是抽象类?

A:首先,抽象类是不允许被实例化的

   其次,抽象类中的抽象方法必须被子类实现

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');

需要注意的是,即使是抽象方法,TypeScript 的编译结果中,仍然会存在这个类。

  

(4)存取器:

TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

例子: 我们把对 fullName的直接访问改成了可以检查密码的 set方法。

目标:我们可以修改一下密码,来验证一下存取器是否是工作的。当密码不对时,会提示我们没有权限去修改员工。

let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

对于存取器有下面几点需要注意的:

首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。

使用 getter 和 setter 可以改变属性的赋值和读取行为:

class Animal {
    constructor(name) {
        this.name = name;
    }
    get name() {
        return 'Jack';
    }
    set name(value) {
        console.log('setter: ' + value);
    }
}

let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack

// 赋值时用到set方法,取值时用到get方法

 


 

二、类与接口:

接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述。

对类的一部分行为进行抽象。

(1)类实现(implements)接口:

实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements关键字来实现。这个特性大大提高了面向对象的灵活性。

举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它

 

一个类只能继承自另一个类,但是可以实现多个接口:

interface Alarm {
    alert();
}

interface Light {
    lightOn();
    lightOff();
}

class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

 

(2)接口继承类:

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)

当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

 

一个接口可以继承多个接口,创建出多个接口的合成接口。

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

例子2:

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}   // 接口继承类

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}  // 理解一下为什么它也算SelectableControl 的子类?  接口的定义:除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
    select() { }
}

class Location {

}

SelectableControl包含了Control的所有成员,包括私有成员state 因为 <