Typescript类静态部分与实例部分的区别-详解

在学习Typescript时,很多人对类接口中的“类静态部分与实例部分的区别”这一章类容比较费解,这里根据实际的案例并辅以说明,帮助大家理解。

前言

关于这一部分,在官网中说的比较晦涩:

https://www.tslang.cn/docs/handbook/interfaces.html

当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内

意思是什么呢?我们将官网上的例子放在编辑器中

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

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

编译不通过,报错原因为

官网给出的解释是:

这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。

由上,我们初步得出了一个初步的结论:类不能直接去实现静态部分的接口,它实际上是对实例部分做类型检查。

这个结论是什么意思,以及如何解决带来的问题,是这一篇文章要说明的重点。

应用场景及业务假设

说到类,就不得不提到javascript es6,在es6版本中首次提到class概念,但其实class只是传统构造函数的语法糖。关于es5的构造函数,相信各位老司机们已经很熟悉了,本文不再做介绍,下面来看下在实际场景中,如何使用js的class(构造函数)来解决问题。

首先来看类的应用场景:类(构造函数)主要用来在创建对象时完成对对象属性的一些初始化等操作,配合工厂模式等可以方便快捷的创建大量的实体。

因此我们假设这么一个简单的应用场景:我们作为一个老师,要录入每个学生的id(id)和年龄(age)(即创建大量的 student对象,对象中有id和age两个字段),现在我们手上已有的条件是:班级id(classId),学生学号(code),学生年龄(age),且学生id的生成规则为:班级id后接学生学号。

已知:班级id(classId),学生学号(code),学生年龄(age)

输出:student:{ id:....,  age:.... }

原始class实现

class Student{
  constructor(classId, code, age){
    this.id = classId + "" + code
    this.age = age
  }
  go(){
    console.log('gogogo')
  }
}

let 小明 = new Student('05', '33', 12)
let 小红 = new Student('05', '12', 11)

以上是在js中的实现,通过使用类Student来进行对象的创建,如果使用typescript,如何对类进行类型检查呢?

typescript约束

实例部分

首先要明白官网中所说的:静态部分与实例部分 分别是哪里。

实例部分指上面代码中的  let 小明 = new Student('05', '33', 12)中,通过new关键字创造的小明,即为实例部分。因此在typescript文档中

当一个类实现了一个接口时,只对其实例部分进行类型检查

意思就是如果我们的class继承了interface接口,那么typescript将会对new出来这个的实例进行检查(而不会去检查class内部的constructor)。 

由上,当我们定义类接口并实现时,首先要考虑到的是生成实例的类型校验:

// 实例部分类型,指那些通过类实例出来的对象,要满足的部分
interface StudentInterface {
  id: string
  age: number
  go():void;
}

这样的约束虽然看起来问题不大, 但是使用者可能会违背接口设定者的本意,在constructor中放飞自我,比如下面这个糟糕的例子:

// 默认参数
let classId = 'errorClassId'
let code = 'defaultCode'
let age = 0

// 实例部分接口
interface StudentInterface {
  id: string
  age: number
  go():void;
}

// 错误的class:将生成逻辑写在了constructor外
class StudentItem implements StudentInterface {
  id: string = classId + code;
  age: number = age;

  // 传入的属性值没有挂载在this上
  constructor(classId: string, code: string, age: number){
    console.log('I am your father')
  }
  go(){
    console.log('gogogo')
  }
}

// 由于class中constructor为空,并没有对传入的三个参数进行操作,因此入参无效
let 小明 = new StudentItem ('05', '33', 12)
console.log(小明)

打印结果为

I am your father
{
    age: 0
    id: "errorClassIddefaultCode
}

这显然是不可理喻的。因此在使用class的时候,我们不仅仅是要约束最终创建的实例,更重要的是要约束类中的constructor构造器,也就是 类的静态部分

静态部分

那么如何用typescript来约束类的静态部分呢?官网上也给出了表示:

interface StudentInfoType {
  // 返回类型是 实例部分 的接口
  new (classId: string, code: string, age: number): StudentInterface;
}

但是仅仅这样是不够的,因为官网已经明确说明:

constructor存在于类的静态部分,所以不在检查的范围内。

因此还要配合一个构造函数,来同时进行两个部分的约束:

// 1.实例部分接口,用来约束最终创建的实例
interface StudentInterface {
  id: string
  age: number
  go():void;
}

// 2.静态部分接口,用来约束构造器声明,返回类型为实例部分接口类型
interface StudentInfoType {
  new (classId: string, code: string, age: number): StudentInterface;
}

// 3.声明用于构造对象的方法(区别于名词‘构造函数’)调用静态部分接口进行实例化对象操作
// 通过调用静态部分接口,来进行接口约束
function createStudent(studentInfo: StudentInfoType, classId: string, code:string, age: number): StudentInterface {
    return new studentInfo(classId, code, age);
}

// 4.声明class,声明时实现 实例部分接口
class StudentItem implements StudentInterface {
  id: string;
  age: number;

  // constructor构造器,内部声明在createStudent中被约束
  constructor(classId: string, code: string, age: number){
    this.id = classId + "" + code
    this.age = age
  }
  go(){
    console.log('gogogo')
  }
}

// 5.传入构造函数来实例化对象,在createStudent中将后续参数通过constructor构造器挂载在实例上
let 小明 = createStudent(StudentItem, '05', '33', 12)
console.log(小明)

运行结果为

{
    age: 12
    id: "0533"
}

运行结果满足期望。

由此,使用typescript对class进行约束声明及使用,一共五个步骤(1/2顺序可以互换):

  1. 声明实例部分接口
  2. 静态部分接口,用来约束构造器声明,返回类型为实例部分接口类型
  3. 声明用于构造对象的方法,调用静态部分接口进行实例化对象操作
  4. 声明class,声明时实现 实例部分接口
  5. 传入构造函数来实例化对象

在typescript中,类的声明和使用一共经过以上五个步骤,对比于最开始的js版本的class,确实繁琐了很多,但是对类进行了严格的限制,更有利于后续的维护。

如果感觉这样写麻烦的话,在网上看到一种更为简单的使用方法:

——感谢博主 kiwi_piggy 在Typescript类静态部分与实例部分的区别_kiwi_piggy的博客-CSDN博客_类静态部分与实例部分的区别fTypescript类静态部分与实例部分的区别@TOC在学习typescript的时候在这一部分看了很久,特地把中文英文文档都看了一遍,在此写下自己的理解先看这个例子interface ClockConstructor { new (hour: number, minute: number);}class Clock implements ClockConstructor {/*报错:Class 'Clock' incorrectly implements interface 'Clockhttps://blog.csdn.net/kiwi_piggy/article/details/108261409

中分享的方法:


interface StudentInterface {
  id: string
  age: number
  go():void;
}

interface StudentInfoType {
  new (classId: string, code: string, age: number): StudentInterface;
}

let createStudent: StudentInfoType = class StudentItem implements StudentInterface{
  id: string;
  age: number;
  constructor(classId: string, code: string, age: number){
    this.id = classId + "" + code
    this.age = age
  }
  go(){
    console.log('gogogo')
  }
}

let 小明 = new createStudent('05', '33', 12)
console.log(小明)

这样一来,逻辑会更加清晰一点,具体使用哪种方式,就见仁见智了。

总结

typescript的class中,静态部分即指class中constructor以及对constructor的接口约束;实例部分是指实例化后的对象以及对实例化后对象的接口约束,在typescript中使用class时,两者结合方能对class进行一个完整的约束。

参考

接口 · TypeScript中文网 · TypeScript——JavaScript的超集 typescript官网文档

Typescript类静态部分与实例部分的区别_kiwi_piggy的博客-CSDN博客_类静态部分与实例部分的区别​​​​​​b

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值