百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 博客教程 > 正文

TS学习笔记三:接口及类 ts中的接口

connygpt 2024-09-25 15:46 5 浏览

本节介绍ts的接口及类相关内容,接口是ts中为类型或第三方代码定义契约,有时被称做“鸭式辨型法”或“结构性子类型化”。

一、接口

Ts是需要对变量等指定类型并进行类型检查,定义方式如下:

interface In{
  a: string;
}
function printA(obj: In) {
  console.log(obj.a);
}
let myObj = {size: 10, a: "Size 10 Object"};
printA(myObj);

类型检查器会查看printA的调用,printA有一个参数,并要求参数必须有一个类型为string的a变量,编译器只会检查那些必须的属性是否存在及类型是否匹配,多余的属性并不会被检测,如上面的例子中传输的myObj中除了a属性还有size属性,但并不会影响参数的传递。

接口就好比一个名字,用来描述了类型的具体结构,上述例子代表了一个类型为string的a属性,这里并不是说myObj实现了In接口,只要传入的对象满足对应的要求,就被允许。

类型检查器不会检查属性的顺序,只要属性存在并且类型对应就可以。

1.可选属性

接口中的属性有些有时候不是必需的,或者在某些情况下需要,这时候就需要使用可选属性的接口,定义方式和普通的接口定义差不多,只是可选属性名字定义的后面加一个?即可,示例如下:

interface Config{
  color?: string;
  width?: number;
}

function create(config: Config): {color: string; area: number} {
  let obj= {color: "white", area: 100};
  if (config.color) {
    obj.color = config.color;
  }
  if (config.width) {
    obj.area = config.width * config.width;
  }
  return obj;
}
let mySquare = create({color: "black"});

可选属性的好处是可以对可能存在的属性进行预定义,也可以捕获引用了不存在的属性时的错误,如属性名若写错误,将会得到一个错误提示:

interface Config{
  color?: string;
  width?: number;
}

function create(config: Config): {color: string; area: number} {
  let obj= {color: "white", area: 100};
  if (config.color) {
    obj.color = config.colorr;//此处将提示属性异常
  }
  if (config.width) {
    obj.area = config.width * config.width;
  }
  return obj;
}
let mySquare = create({color: "black"});

2.只读属性

只读属性是只有在创建时设置其值,之后将不可修改,在属性前面添加readonly进行指定,定义方式如下:

interface P{
    readonly x: number;
    readonly y: number;
}

创建对象时,给x和y进行赋值,赋值后将不可修改,如:

let p1: P= { x: 10, y: 20 };
p1.x = 5; // 此处将会保存,因为x属性是只读的。

TS中有特殊的只读数组,定义方式为:ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // 此处将报错
ro.push(5); // 此处将报错
ro.length = 100; // 此处将报错
a = ro; // 此处将报错

可以使用类型断言进行重写:

a = ro as number[];

readonly和const的区别:主要判断是用作变量还是属性,作为变量是使用const,作为属性时使用readonly。

3.额外的属性检查

interface Config {
    color?: string;
    width?: number;
}

function create(config: Config ): { color: string; area: number } {

    // ...

}
let mySquare = create({ colour: "red", width: 100 });

此实例中传输的参数中color属性拼写为了colour,这种按照可选属性的写法是可以,但是实际情况是会报错,因为对象的字面量会被特殊对待,而且会经过额外属性检查,即当将它们赋值给变量或作为参数传递的时候,如果一个对象字面量存在任何目标类型不包含的属性时,将会报错。

// 此处将报错,因为Config中没有colour的属性。
let mySquare = create({ colour: "red", width: 100 });

需要绕开的话,第一种方式是:可以使用类型断言进行处理:

let mySquare = create({ width: 100, opacity: 0.5 } as Config);

第二种方式是:若能确定这个对象可能具有某些作为特殊用途使用的额外属性,最佳的方式是添加一个字符串索引签名,如下:

interface Config {
    color?: string;
    width?: number;
    [propName: string]: any;
}

上述例子中表示Config可以有任意数量的属性,并且只要名称不是color或width,则其类型无所谓。

第三种方式是可以将对象赋值给另一个变量,因为另一个变量不会经过额外属性检查,所以可以绕开,实例如下:

let options = { colour: "red", width: 100 };
let mySquare = create(options );

4.函数类型

接口能够描述js中对象的外形,除了描述带有属性的普通对象外,也可以描述函数的类型,定义方式如下:

interface Fun{
  (a: string, b: string): boolean;
}

定义后,可以像使用其他接口一样使用这个函数类型的接口,定义方式如下:

let mySearch: Fun;
mySearch = function(a: string, b: string) {
  let result = a.search(b);
  if (result == -1) {
    return false;
  }
  else {
    return true;
  }
}

函数中参数的名称可以和接口中定义的名称不一致,如:

let mySearch: Fun;
mySearch = function(c: string, d: string): boolean {
  let result = c.search(d);
  if (result == -1) {
    return false;
  }
  else {
    return true;
  }
}

函数的参数检查时,会逐个进行,要求对应位置上的类型是兼容的,若不指定类型,ts会自动推断出参数的类型,示例如下:

let mySearch: Fun;
mySearch = function(a, b) {
    let result = a.search(b);
    if (result == -1) {
        return 1;
    }
    else {
        return 2;
    }
}

上述示例中将会警告函数的返回值与接口中的定义不匹配。

5.可索引的类型

可以描述能够通过索引得到的类型,如a[10]或a[‘b’],可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型,如下:

interface Test{
  [index: number]: string;
}

let myArray: Test;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

上述示例中Test具有索引签名,描述了使用number类型去索引Test时会得到string类型的返回值。

只支持字符串和数字两种索引签名,可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。因为使用number来索引时,js会将它转换成string然后再去索引对象,示例如下:

class A{
    name: string;
}

class B extends A{
    breed: string;
}

// 此处将报错,因为索引必须时number或string的,不能使用其他类型。

interface NotOkay {
    [x: number]: A;
    [x: string]: B;
}

字符串索引签名会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了 obj.property和obj["property"]两种形式。下面示例中name的类型与字符串索引类型不匹配,会得到异常提示。

interface A{
  [index: string]: number;
  length: number;    // 可以,length是number类型
  name: string       // 错误,`name`的类型不是索引类型的子类型
}

可以将索引签名设置为只读,这样就防止了给索引赋值:

interface A{

readonly [index: number]: string;

}

let myArray: A= ["Alice", "Bob"];

myArray[2] = "Mallory"; // 此处的索引签名是只读的,所以不能直接设置

6.类类型

实现接口,ts中可以明确的强制一个类去符合某种契约,如下:

interface A{
    time: Date;
}

class Clock implements A{
    time: Date;
    constructor(h: number, m: number) { }
}

也可以在接口中描述一个方法,在类中实现它,示例如下:

interface A{
    time: Date;
    setTime(d: Date);

}

class Clock implements A{
    time: Date;
    setTime(d: Date) {
        this.time= d;
    }
    constructor(h: number, m: number) { }
}

接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。

类具有静态部分类型和实例部分类型,当用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:

interface A{
    new (hour: number, minute: number);
}

class Clock implements A{
    currentTime: Date;
    constructor(h: number, m: number) { }
}

当一个类实现了一个接口时,只对其实例部分进行类型检查。 构造函数constructor存在于类的静态部分,所以不在检查的范围内。若需要检测构造函数中参数,可使用以下方式:

interface AConstructor {
    new (hour: number, minute: number): AInterface ;
}
interface AInterface {
    tick();
}
function create(ctor: AConstructor , hour: number, minute: number): AInterface  {
    return new ctor(hour, minute);
}
class B implements AInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class C implements AInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}
let digital = create(B, 12, 17);
let analog = create(C, 7, 32);

上述示例中create的第一个参数是AConstructor 类型,在create里,会检查参数是否符合构造函数签名。

7.扩展接口

接口可以相互扩展,可以将属性从一个接口复制到另一个接口,如下:

interface A{
    color: string;
}
interface B extends A{
    sideLength: number;
}
let square = <A>{};
square.color = "blue";
square.sideLength = 10;

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

interface A{
    color: string;
}
interface B{
    penWidth: number;
}
interface C extends A, B{
    sideLength: number;
}
let square = <A>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

8.混合类型

一个接口可以同时作为函数和对象使用,并带有额外的属性,在使用js第三方库的时候,也可以完整的定义对应的类型,如下:

interface C{
    (start: number): string;//函数
    interval: number;//属性
    reset(): void;//函数
}

function getC(): C{
    let counter = <C>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}
let c = getC();
c(10);
c.reset();
c.interval = 5.0;

9.接口继承类

当接口继承了一个类类型时,会继承类的成员,但不包括函数的具体实现,类似于声明了类中的所有成员,但并没有提供具体的实现。

接口也会继承类的private和protected成员,即当创建了一个接口,此接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类实现。

class C{
    private state: any;
}

interface D extends C{
    select(): void;
}

class B extends C implements D{
    select() { }
}

class T extends C{
    select() { }
}

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

上述示例中接口D包含了C的所有成员,包括私有成员state,因为state是私有成员,所以只有C的子类才能实现D接口,因为只有C的子类才能够拥有一个声明与C的私有成员state。在C类内部,允许通过D的实例来访问私有成员state的,D就像C一样,并拥有一个select方法,B和T类是D的子类,所以可以正常定义,但E类并没有继承C,所以会报错。

二、类

JS使用函数和基于原型的继承来创建可重用的组件,从ES6开始,js也能够使用基于类的面向对象的方式。定义方式如下:

class C{
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new C("world");

引用类成员的时候需要使用this,表示访问的是类的成员。

1.继承

可以使用继承来扩展现有的类,使用方式如下:

class A{
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class B extends A{
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class C extends A{
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new B("Sammy the Python");
let tom: A= new C("Tommy the Palomino");

sam.move();
tom.move(34);

使用extends关键字进行继承定义,子类可以访问父类的属性和方法,子类的构造函数中必须调用super(),会执行基于基类的构造方法,子类也可以重写父类的方法。

2.修饰符

修饰符包括公共/私有和受保护的类型,分别是public/private/protected。

public修饰符:

ts中默认的修饰符是public,也可以明确的将一个成员标记成public,实例如下:

class A{
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

private修饰符:

当成员被标记成private时,就不能在声明它的类的外部访问,示例如下:

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

new A("Cat").name; //此处将报错,因为name属性时私有的,所以不可以访问。ts中比较两种不同的类型时,并不在乎它们从何处而来,如果所有的类型都是兼容的,就认为他们的类型是兼容的。

比较带有private和protected成员类型的时候,如果一个类型里包含一个private成员,那么只有当另一个类型中也存在这样的一个private成员,并且它们都来自同一处声明时,才认为这两个类型是兼容的,protected修饰符也是同样的规则,示例如下:

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

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

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

let animal = new A("Goat");
let rhino = new B();
let employee = new C("Bob");
animal = rhino;
animal = employee; // 此处将会报错,因为C类没有继承A,虽然有相同的private属性,但是它们的声明却不是同一个地方,所以是不兼容的。

protected修饰符:

protected修饰符和private修饰符类似,不同的地方就是protected成员在派生类中任然可以访问,示例如下:

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

class B extends A{
    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 B("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 此处将报错,因为name是私有成员

不能在A类外使用name,但可以使用B的实例方法访问,因为B是A派生而来的。构造函数也可以被标记为protected,标记后这个类将不能在包含它的类外被实例化,但是能被继承,如:

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

// 此处能继承
class B extends A{
    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 B("Howard", "Sales");
let john = new A("John"); // 此处将报错,因为A的构造函数是受保护的,不能在其外部访问。

readonly修饰符

readonly修饰符能将属性设置为只读的,设置后此属性必须在声明时或构造函数里被初始化,其他地方无法进行修改值,示例如下:

class O{
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}

let dad = new O("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 此处将报错,因为属性name是只读的,所以无法进行重新赋值。

3. 参数属性

定义成员属性也可以通过参数属性的方式简写,参数属性可以在一个地方定义并初始化一个成员,示例如下:

class A{
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

上述示例中省略了属性的定义及构造函数中赋值的操作,将声明及赋值合并到了一起,参数属性通过给构造函数中参数添加一个访问限定符进行声明。

4.存取器

ts支持通过getters/setters来截取对象成员的访问,能有效的控制对对象成员的访问,p普通使用方式如下:

class A{
    fullName: string;
}

let a= new A();
a.fullName = "Bob Smith";
if (a.fullName) {
    console.log(a.fullName);
}

可以将上述示例改写成get/set方式进行处理,示例如下:

let a= "secret passcode";
class A{
    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 a= new A();
a.fullName = "Bob Smith";
if (a.fullName) {
    alert(a.fullName);
}

存取器要求编辑器的输出设置为ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有set的存取器自动被推断为readonly。

5.静态属性

静态属性是存在与类本身而不是类的实例上,而实例成员是当类被实例化的时候才会被初始化的属性。静态属性使用static进行声明,访问时必须加上对应的类名才能进行访问,实例属性上使用this.来访问属性,实例如下:

class A{
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new A(1.0);  // 1x scale
let grid2 = new A(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

6.抽象类

抽象类是为其派生类的基类使用,不会被实例化,不同于接口,抽象类可以包含成员的实现细节,关键字是abstract,可以用于定义抽象类和抽象类内部抽象方法。

abstract class A{
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}

抽象类中的抽象方法不能包含具体的实现,而且需要在派生类中实现具体的内容,语法和接口方法类似,都是包含方法签名但不好含方法实现,抽象方法必须使用abstract 进行声明,实例如下:

abstract class A{
    constructor(public name: string) {
    }
    printName(): void {
        console.log('Department name: ' + this.name);
    }
    abstract printMeeting(): void; // 必须在派生类中实现
}

class B extends A{

    constructor() {
        super('Accounting and Auditing'); // constructors in derived classes must call super()
    }

    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }

    generateReports(): void {
        console.log('Generating accounting reports...');
    }

}

let department: A; // ok to create a reference to an abstract type
department = new A(); // error: 抽象类不能被直接初始化
department = new B(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: 抽象类中的方法不存在

7.构造函数

ts声明一个类的时候,也就声明了类的实例的类型,示例如下:

class A{
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}
let greeter: A;
greeter = new A("world");
console.log(greeter.greet());

构造函数会在使用new创建类实例的时候被调用。

class A{
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {
        if (this.greeting) {
            return "Hello, " + this.greeting;
        }else {
            return Greeter.standardGreeting;
        }
    }
}

let greeter1: A;
greeter1 = new A();
console.log(greeter1.greet());

let greeterMaker: typeof A= A;
greeterMaker.standardGreeting = "Hey there!";
let greeter2: A= new greeterMaker();
console.log(greeter2.greet());

上述实例中定义了greeterMaker变量,变量的类型是A类的的构造函数,使用typeof A是指取A类的类型,而不是实例的类型,即构造函数的类型,这个类型包含了类的所有静态成员和构造函数,之后可以使用new关键字创建A类的实例。

8.类当作接口

类会创建实例类型和一个构造函数,因为类可以创建出类型,所以能够允许使用接口的地方使用类:

class Point {
    x: number;
    y: number;
}
interface Point3d extends Point {
    z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

相关推荐

3分钟让你的项目支持AI问答模块,完全开源!

hello,大家好,我是徐小夕。之前和大家分享了很多可视化,零代码和前端工程化的最佳实践,今天继续分享一下最近开源的Next-Admin的最新更新。最近对这个项目做了一些优化,并集成了大家比较关注...

干货|程序员的副业挂,12个平台分享

1、D2adminD2Admin是一个完全开源免费的企业中后台产品前端集成方案,使用最新的前端技术栈,小于60kb的本地首屏js加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助...

Github标星超200K,这10个可视化面板你知道几个

在Github上有很多开源免费的后台控制面板可以选择,但是哪些才是最好、最受欢迎的可视化控制面板呢?今天就和大家推荐Github上10个好看又流行的可视化面板:1.AdminLTEAdminLTE是...

开箱即用的炫酷中后台前端开源框架第二篇

#头条创作挑战赛#1、SoybeanAdmin(1)介绍:SoybeanAdmin是一个基于Vue3、Vite3、TypeScript、NaiveUI、Pinia和UnoCSS的清新优...

搭建React+AntDeign的开发环境和框架

搭建React+AntDeign的开发环境和框架随着前端技术的不断发展,React和AntDesign已经成为越来越多Web应用程序的首选开发框架。React是一个用于构建用户界面的JavaScrip...

基于.NET 5实现的开源通用权限管理平台

??大家好,我是为广大程序员兄弟操碎了心的小编,每天推荐一个小工具/源码,装满你的收藏夹,每天分享一个小技巧,让你轻松节省开发效率,实现不加班不熬夜不掉头发,是我的目标!??今天小编推荐一款基于.NE...

StreamPark - 大数据流计算引擎

使用Docker完成StreamPark的部署??1.基于h2和docker-compose进行StreamPark部署wgethttps://raw.githubusercontent.com/a...

教你使用UmiJS框架开发React

1、什么是Umi.js?umi,中文可发音为乌米,是一个可插拔的企业级react应用框架。你可以将它简单地理解为一个专注性能的类next.js前端框架,并通过约定、自动生成和解析代码等方式来辅助...

简单在线流程图工具在用例设计中的运用

敏捷模式下,测试团队的用例逐渐简化以适应快速的发版节奏,大家很早就开始运用思维导图工具比如xmind来编写测试方法、测试点。如今不少已经不少利用开源的思维导图组件(如百度脑图...)来构建测试测试...

【开源分享】神奇的大数据实时平台框架,让Flink&amp;Spark开发更简单

这是一个神奇的框架,让Flink|Spark开发更简单,一站式大数据实时平台!他就是StreamX!什么是StreamX大数据技术如今发展的如火如荼,已经呈现百花齐放欣欣向荣的景象,实时处理流域...

聊聊规则引擎的调研及实现全过程

摘要本期主要以规则引擎业务实现为例,陈述在陌生业务前如何进行业务深入、调研、技术选型、设计及实现全过程分析,如果你对规则引擎不感冒、也可以从中了解一些抽象实现过程。诉求从硬件采集到的数据提供的形式多种...

【开源推荐】Diboot 2.0.5 发布,自动化开发助理

一、前言Diboot2.0.5版本已于近日发布,在此次发布中,我们新增了file-starter组件,完善了iam-starter组件,对core核心进行了相关优化,让devtools也支持对IAM...

微软推出Copilot Actions,使用人工智能自动执行重复性任务

IT之家11月19日消息,微软在今天举办的Ignite大会上宣布了一系列新功能,旨在进一步提升Microsoft365Copilot的智能化水平。其中最引人注目的是Copilot...

Electron 使用Selenium和WebDriver

本节我们来学习如何在Electron下使用Selenium和WebDriver。SeleniumSelenium是ThoughtWorks提供的一个强大的基于浏览器的开源自动化测试工具...

Quick &#39;n Easy Web Builder 11.1.0设计和构建功能齐全的网页的工具

一个实用而有效的应用程序,能够让您轻松构建、创建和设计个人的HTML网站。Quick'nEasyWebBuilder是一款全面且轻巧的软件,为用户提供了一种简单的方式来创建、编辑...