TypeScript 之 interface和class

工具: PlayGround


interface接口


interface 用来定义对象的结构和类型,描述对象应该具有哪些属性和方法。 注意:

  • 它仅用于声明,而不是实现; 这对于编写可重用的代码非常有用
  • 它只是作为TypeScript的一部分,并不会转换为JavaScript

可用于:

  1. 数组定义
interface data {
    [index:number]: number,
}

// 类型一致,没有问题
let numList_1: data = [1, 2, 3];
// Type 'string' is not assignable to type 'number'
let numList_2: data = [1, "", 3];
  1. 对象的定义
// 方式1
interface Data_1 {
    name: string,
    age: number,
}
let data_1: Data_1 = {
    name: "hello",
    age: 20,
}
console.log(data_1);

// 方式2
interface Data_2 {
    name: string;
    age: number;
    // 可选属性, 表示可以选择不赋值, 该属性也可以用在作为函数参数使用
    sex?: number;
    // 只读属性,表示一旦赋值后不可修改                       
    readonly id:number | string;   
}
let data_2: Data_2 = {
    name: "hello",
    age: 20,
    id : "0001",
}
data_2.age = 10;
// Error: Cannot assign to 'id' because it is a read-only property
data_2.id = 10;

  1. 作为类实现
interface Person {
  name: string;
  age: number;
  greet: () => void;
}

// 通过implements实现接口
class Student implements Person {
  name: string;
  age: number;

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

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

let student = new Student("ok", 20);
student.greet();        // "Hello, my name is ok" 

在实际的开发中,函数传参是常用的操作,如果单纯通过注释说明,可能代码多次修改后,导致不容易维护,推荐使用interface

interface Person {
    name: string | null,    // 名字
    age: any,     					// 岁数
    sex: number,            // 性别:0.男 1.女
    scoreList?: number[],   // 分数列表
}

function createPerson(param: Person) {
    if (!param) {
        return console.error("param is null");
    }
    let strName = param.name || "";
    let age = Number(param.age) || 0;
    let sex = (param.sex == 1) ? true : false;
    console.log(strName, age, sex);
}

let params: Person = {
    name: "TypeScript",
    age: 1000,
    sex: 0,
    scoreList: [100, 90, 80],
}
// "TypeScript",  1000,  false
createPerson(params);

在上述的代码中对数据使用了Number的转换,主要原因在于:

// 如果数据一定为string类型,推荐使用parseInt
let age = Number.parseInt(param.age);

// 如果数据类型为any类型,推荐使用Number构造函数转换
let age = Number(param.age) || 0;


TypeScript 中类的使用,主要通过class来创建类对象,通过new来实例化类对象, 支持:

  • instanceof检测对象类型
  • 访问权限publicprotectedprivate
  • 函数重载
  • 继承或重写
  • 静态成员和方法

class的本质依然是function,但重点是将对象或函数模块化

注: 在ES6中,对parseInt、pareseFloat 整合到了Number中,就是对模块的一种整理和优化

class Demo {
    private _name: string = "";
    private _age: number = 0;

    // 构造函数
    constructor(name:string) {
        this._name = name;
    } 

    public log() {
        console.log(`Demo 的名字为${this._name}`);
    }
}
let demo = new Demo("demo");
console.log(typeof(demo));              // "object" 
console.log(demo instanceof Demo);      // true    

// 获取类名,可使用name
class Demo_1 {}
class Demo_2 {}
// "Demo_1",  "Demo_2" 
console.log(Demo_1.name, Demo_2.name); 

注:在类中没有声明public/private/protected的话,默认为public


set/get对象

gettersetter可用于获取和设置类中的私有属性,使用它们注意:

  • 必须在一个类中
  • 不能用于静态属性
  • 函数名字必须相同
class Demo {
    private _name: string;
    constructor(name: string) {
        this._name = name;
    }

    get Name():string {
        return this._name;
    }

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

const demo = new Demo("JavaScript");
// 获取私有属性, 
console.log(demo.Name);     // "JavaScript"
// 设置属性
demo.Name = "TypeScript";
console.log(demo.Name);     // "TypeScript" 

注:通过 set/get 对属性值的设置/获取,这是 ECMAScript 5ES6 的特性, 原理是属性值会被定义为一个存取器,通过 set/get 会生成对应的方法来完成设置和获取。


函数重载

类的函数重载的编写方式,不要直接实现:

class Demo {
  private _name: string = "";
  private _age: number = 0;

  // 构造函数
  constructor(name:string) {
    //
  } 

  // Error: Multiple constructor implementations are not allowed
  // 原因在于如果实现,编译器创建对象不知道调用哪个构造函数
  constructor(name: string, age:number) {
    //
  }
}

可以通过采用声明和可选参数的方式来实现:

// 实例1
class Demo {
  private _name: string = "";
  private _age: number = 0;

  // 构造函数
  constructor(name:string);
  constructor(name: string, age:number);
  constructor(name: string, age?:number) {
    this._name = name;
    if (age) {
      this._age = age;
    }
  }
}

// 实例2:
class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: any, b: any): any {
    if (typeof a === 'number' && typeof b === 'number') {
      return a + b;
    } else if (typeof a === 'string' && typeof b === 'string') {
      return a.concat(b);
    } else {
      throw new Error('Invalid arguments');
    }
  }
}

const calculator = new Calculator();

console.log(calculator.add(1, 2));      // 3
console.log(calculator.add('Hello', ' World')); // Hello World

Extends继承和重写

继承的话,使用extends, 如果对基类中的方法重新编写,就是重写

// 定义接口,简单的实例可以忽略;增加的话,有助于理解代码和重写代码相关
interface Animal {
    sound(): void;
}

// 实现接口
class Cat implements Animal {
    private _breed: string;
    private _name: string = "Cat";

    constructor(breed: string) {
        this._breed = breed;
    }

    // 通过get和set的方式设置对象变量属性
    get Name():string {
        return this._name;
    }
    set Name(name: string) {
        this._name = name;
    }

    sound(): void {
        console.log("猫");
    }
}

// 继承
class DomesticCat extends Cat {
    public log() {
        console.log("this is DomesticCat");
    }
}

// 重写
class WildCat extends DomesticCat {
    sound(): void {
        console.log("野猫叫声");
    }
}

const domesticCat = new DomesticCat("domesticCat");
domesticCat.Name = "短毛猫"
console.log(`cat Name: ${domesticCat.Name}`);   //"cat Name: 短毛猫" 
domesticCat.sound(); // 猫

// 
const wildCat = new WildCat("豹猫");
wildCat.sound();    // 野猫叫声

注意:

  • 对于变量,建议开头增加下划线_ 表示私有
  • 可以多继承,只需要在extends后面添加类接口即可

注意区分implementsextends,前者主要用于对interface声明的方法等进行实现, 而后者主要应用于已经实现后的方法进行继承或重写。

// 声明接口
interface MyInterface {
  myMethod(): void;
}

// 实现接口
class MyClass implements MyInterface {
  myMethod() {
    console.log("Implementing MyInterface");
  }
}

const myObj = new MyClass();
myObj.myMethod(); // 输出 "Implementing MyInterface"

// interface 也可以用来定义数据结构
interface Data {
    index: number,
    name: string,
    dataList: any[],
    map: {},
}

let data:Data;
data = {
    index: 1,
    name : "typeScript",
    dataList: [1, "2", true],
    map: {a: 1, b:2},
};
console.log(data);

super

主要在子类中使用,调用基类的构造函数和方法相关

class Base {
  constructor() {
    console.log("Base constructor");
  }

  public Message() {
    console.log("Base Message");
  }
}

class Demo extends Base {
  constructor() {
    // 使用在构造函数中,必须有super
    // 否则报错:Constructors for derived classes must contain a 'super' call
    super(); 
    console.log("Demo constructor");
  }

  public Message() {
    // 可用在其他函数中调用基类的public方法相关, 不可为私有方法,否则报错
    super.Message();
  }
}

const demo = new Demo();
demo.Message();

/*
"Base constructor"
"Demo constructor"
"Base Message" 
*/

Static

静态变量或方法在类中声明后,可以不通过new对象直接赋值或使用,但赋值后不可在改变。

class Demo {
    static value: number;
    static getValue() {
        console.log(`static value: ${Demo.value}`);
    }
}
Demo.value = 10;
Demo.getValue();        //"static value: 10" 

它常用于编写单例,特点: 为保证对象的唯一性,构造函数设置为私有

class Singleton {
  private static instance: Singleton;

  private constructor() {
    // 私有化构造函数,防止外部实例化
  }

  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  public greet(): void {
    console.log("Hello");
  }
}

// 创建单例实例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // true

instance1.greet(); // "Hello" 
instance2.greet(); // "Hello" 

const

作为常量使用,在类中使用注意:

  • 只能用于修改基本数据类型和对象属性字面量,不可用于修饰引用类型
  • 如果声明const 一定要记得初始化,不能在构造函数或其他函数进行初始化
  • 声明以后,其本质就是readonly 只读属性
class MyClass {
  readonly PI: number = 3.14;
  readonly config: { name: string, age: number } = { name: "John", age: 25 };

  constructor() {
    // this.PI = 3.14159;  // 错误,无法对只读属性进行重新赋值
    // this.config = { name: "Alice", age: 30 }; // 错误,无法对只读属性进行重新赋值

    this.config.name = "Alice"; // 正确,可以修改对象字面量属性的值
    this.config.age = 30; // 正确,可以修改对象字面量属性的值
  }

  printInfo(): void {
    console.log(`PI: ${this.PI}`);
    console.log(`Name: ${this.config.name}, Age: ${this.config.age}`);
  }
}

const myObj = new MyClass();
myObj.printInfo();

// "PI: 3.14" 
// "Name: Alice, Age: 30" 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤九日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值