当前位置:网站首页>【TypeScript】深入学习TypeScript类(下)
【TypeScript】深入学习TypeScript类(下)
2022-08-02 21:26:00 【海底烧烤店ai】
TypeScript学习:TypeScript从入门到精通
TypeScript类(上篇):TypeScript类(上篇)
蓝桥杯真题解析:蓝桥杯Web国赛真题解析
个人简介:即将大三的学生,热爱前端,热爱生活
你的一键三连是我更新的最大动力️!
前言
最近博主一直在创作
TypeScript
的内容,所有的TypeScript
文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅️
本篇文章将继续去讲解TypeScript
中的class
类(由于内容较多,将其分为了上下两篇,查看上篇内容请点击这里),这也许会是你看过的最全面最细致的TypeScript
教程,点赞关注收藏不迷路!
1、泛型类
类和接口一样,可以是泛型的,当一个泛型类用new
实例化时,其类型参数的推断方式与函数调用的方式相同:
class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}
// const b: Box<string>
const b = new Box("hello!");
// 等同于const b = new Box<string>("hello!");
泛型类的静态成员不能引用类型参数:
2、this指向
在JavaScript
中this
指向是一个头疼的问题,默认情况下函数内this
的值取决于函数的调用方式,在一些情况下这会出现意向不到的效果,如下方代码:
class MyClass {
name = "MyClass";
getName() {
return this.name;
}
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};
// 输出 "obj", 而不是 "MyClass"
console.log(obj.getName());
TypeScript
提供了一些方法来减少或防止这种错误:
箭头函数
class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};
// 输出 "MyClass", 而不是 "obj"
console.log(obj.getName());
使用箭头函数也是有一些妥协的:
this
值保证在运行时是正确的,即使是没有经过TypeScript
检查的代码也是如此这将使用更多的内存,因为每个类实例将有它自己的副本,每个函数都是这样定义的
你不能在派生类中使用
super
调用基类方法,因为在原型链中没有入口可以获取基类方法:class MyClass { name = "MyClass"; getName = () => { return this.name; }; } class A extends MyClass { AName: string; constructor() { super(); // getName为箭头函数时,调用super.getName()会报错 // this.AName = super.getName(); this.AName = this.getName(); // 但一直能通过this.getName()调用 } } const a = new A(); console.log(a.AName); // MyClass
this参数
在【TypeScript】深入学习TypeScript函数中我们提到过this
参数,TypeScript
检查调用带有this
参数的函数,是否在正确的上下文中进行
我们可以不使用箭头函数,而是在方法定义中添加一个this
参数,以静态地确保方法被正确调用:
class MyClass {
name = "MyClass";
getName(this: MyClass) {
return this.name;
}
}
const c = new MyClass();
// 正确
c.getName();
// 错误
const g = c.getName;
console.log(g());
这种方法做出了与箭头函数方法相反的取舍:
JavaScript
调用者仍然可能在不知不觉中错误地使用类方法,如上面的例子:class MyClass { name = "MyClass"; getName(this: MyClass) { return this.name; } } const c = new MyClass(); const obj = { name: "obj", getName: c.getName, }; // 依旧输出 "obj", 而不是 "MyClass" console.log(obj.getName());
每个类定义只有一个函数被分配,而不是每个类实例一个函数
基类方法定义仍然可以通过
super
调用。
3、this类型
在类中,一个叫做 this
的特殊类型动态地指向当前类的类型,看下面的这个例子:
class Box {
contents: string = "";
set(value: string) {
this.contents = value;
return this;
}
}
在这里,TypeScript
推断出 set
方法的返回类型是this
,而不是Box
:
创建Box
的一个子类:
class ClearableBox extends Box {
clear() {
this.contents = "";
}
}
const a = new ClearableBox(); // a类型为ClearableBox
const b = a.set("hello"); // b类型为ClearableBox
console.log(b);
这里可以看到b
的类型竟然是ClearableBox
,这说明此时set
方法返回的this
类型指向了当前的类ClearableBox
(因为是在ClearableBox
上调用的set
)
可以在参数类型注释中使用this
:
class Box {
contents: string = "";
// 类型注释中使用this
sameAs(other: this) {
return other.contents === this.contents;
}
}
class ClearableBox extends Box {
contents: string = "Ailjx";
}
class B {
contents: string = "";
}
const box = new Box();
const clearableBox = new ClearableBox();
const b = new B();
console.log(clearableBox.sameAs(box)); // false
// 报错
// 类型“B”的参数不能赋给类型“ClearableBox”的参数
// 类型 "B" 中缺少属性 "sameAs",但类型 "ClearableBox" 中需要该属性
console.log(clearableBox.sameAs(b));
上面例子中可以看到派生类ClearableBox
的sameAs
方法能够接收基类的实例
但是当派生类中有额外的属性后,它就只能接收该同一派生类的其它实例了:
class Box {
contents: string = "";
sameAs(other: this) {
return other.contents === this.contents;
}
}
class ClearableBox extends Box {
otherContents: string = "Ailjx";
}
const box = new Box();
const clearableBox = new ClearableBox();
// 报错:
// 类型“Box”的参数不能赋给类型“ClearableBox”的参数。
// 类型 "Box" 中缺少属性 "otherContents",但类型 "ClearableBox" 中需要该属性。
console.log(clearableBox.sameAs(box));
4、基于类型守卫的this
我们可以在类和接口的方法的返回位置使用类型谓词this is Type
,当与类型缩小混合时(例如if
语句),目标对象的的类型将被缩小到指定的Type
class Box {
// 利用类型谓词,当this类型是A的实例时,确保将this类型缩小为A类型
isA(): this is A {
return this instanceof A;
}
isB(): this is B {
return this instanceof B;
}
}
class A extends Box {
Apath: string = "A";
}
class B extends Box {
Bpath: string = "B";
}
// fso的类型为基类Box,它可能是A,也可能是B
const fso: Box = Math.random() > 0.5 ? new A() : new B();
if (fso.isA()) {
// fso.isA()为true时(说明Box的this类型指向了A,即可知道此时fso具体为A),
// 其通过类型谓词将fso缩小为了A类型,此时就可以安全调用A特有的属性
console.log(fso.Apath);
} else if (fso.isB()) {
console.log(fso.Bpath);
}
配合接口使用:
class Box {
isNetworked(): this is Networked & this {
return this.networked;
}
// networked属性控制Box是否包含Networked接口类型
constructor(private networked: boolean) {
} // 这里使用了在构造器参数列表中声明属性
}
interface Networked {
host: string;
}
const A: Box = new Box(true);
// A.host = "12"; // 外界直接使用host属性报错:类型“Box”上不存在属性“host”
if (A.isNetworked()) {
// 此时A类型变成了Networked & this,可以安全使用host属性了
A.host = "12";
console.log(A.host); // 12
}
基于 this
的类型保护的一个常见用例,是允许对一个特定字段进行懒惰验证。例如,这种情况下,当hasValue
被验证为真时,Box
类型缩小,value
属性失去了可选性,就能直接使用了:
class Box<T> {
value?: T;
// 根据value值是否存在来缩小类型
hasValue(): this is {
value: T } {
return this.value !== undefined;
}
}
const box = new Box<string>();
// value可能未定义需要使用可选连?
console.log(box.value?.toUpperCase());
if (box.hasValue()) {
// 这时Box类型已经缩小为{value:string}了,value不再是可选属性了,可以不使用可选连?了
console.log(box.value.toUpperCase());
}
5、类表达式
类表达式与类声明非常相似,唯一真正的区别是,类表达式不需要一个名字,我们可以通过它们最终绑定的任何标识符来引用它们:
const someClass = class<Type> {
content: Type;
constructor(value: Type) {
this.content = value;
}
};
// type m=someClass<string>
const m = new someClass("Hello, world");
6、抽象类和成员
使用abstract
定义的一个方法或字段称为抽象成员,它是一个没有提供实现的方法或字段,这些成员必须存在于一个使用abstract
定义的抽象类中,该类不能直接实例化:
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
// 报错:无法创建抽象类的实例
const b = new Base();
抽象类的作用是作为子类的基类,实现所有的抽象成员:
// 创建一个派生类实现抽象成员
class Derived extends Base {
getName() {
return "world";
}
}
const d = new Derived();
d.printName(); // Hello world
如果抽象类的派生类不实现它的抽象成员则会报错:
抽象构造签名
向上面这个例子,如果你想要写一个函数,能够接受所有抽象类Base
的派生类,你可能会这样写:
function greet(ctor: typeof Base) {
const instance = new ctor();
instance.printName();
}
这时TypeScript
会告诉你这样写是不对的:
正确的做法应该是使用抽象构造签名:
function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
完整示例:
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
class Derived extends Base {
getName() {
return "world";
}
}
class Derived2 extends Base {
getName() {
return "world2";
}
}
function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Derived2);
// 报错:类型“typeof Base”的参数不能赋给类型“new () => Base”的参数。
// 无法将抽象构造函数类型分配给非抽象构造函数类型。
greet(Base);
7、类之间的关系
相同的类可以互相替代使用:
class Point1 { x = 0; y = 0; } class Point2 { x = 0; y = 0; } // 正确 const p: Point1 = new Point2();
即使没有明确的继承,类之间的子类型关系也是存在的:
class Person { name: string = "A"; age: number = 1; } class Employee { name: string = "A"; age: number = 1; salary: number = 99; } // type A = number type A = Employee extends Person ? number : string; // 正确 const p: Person = new Employee();
空的类通常是其他任何东西的基类:
class Person { name: string = "A"; age: number = 1; } class Employee { salary: number = 99; } class N { } // type A = number type A = Person extends N ? number : string; // type B = number type B = Employee extends N ? number : string; function fn(x: N) { } // 以下调用均可 fn(Person); fn(Employee); fn(window); fn({ }); fn(fn);
结语
至此,TypeScript
类的内容就全部结束了,关注博主下篇更精彩!
博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。
参考资料:TypeScript官网
如果本篇文章对你有所帮助,还请客官一件四连!️
边栏推荐
猜你喜欢
《分布式微服务电商》专题(一)-项目简介
【目标检测】YOLOv5:640与1280分辨率效果对比
ECCV 2022 | ByteTrack: 简单高效的数据关联方法
What is the core business model of the "advertising e-commerce" that has recently become popular in the circle of friends, and is the advertising revenue really reliable?
用户之声 | 我与GBase的缘分
Byte's internal technical map is amazing and practical
Swin Transformer 论文精读,并解析其模型结构
【3D视觉】深度摄像头与3D重建
圆锥折射作为偏振计量工具的模拟
CS5213芯片|HDMI to VGA转换头芯片资料分享
随机推荐
源码构建LAMP环境-3
2022-08-02 第六小组 瞒春 学习笔记
搭建Spark开发环境(第二弹)
拥抱Cmake小朋友 简单又实用,但是不灵活
# 医院管理系统完整项目代码以及数据库建表语句分享
2018HBCPC个人题解
快速学会ansible的安装
hi!Don't look at how to SAO gas dye-in-the-wood in MySQL?
正则表达式
我用这一招让团队的开发效率提升了 100%!
golang刷letcode:公司命名
Interviewer: can you talk about optimistic locking and pessimistic locks
SSM整合步骤(重点)
js: 实现一个cached缓存函数计算结果
“百日行动”进行时:700余交通安全隐患被揪出
命令行启动常见问题及解决方案
Byte's internal technical map is amazing and practical
UDP(用户数据报协议)
The interviewer asked me: delete library, in addition to run do?
I interviewed a 985 graduate, and I will never forget the expression when answering the "performance tuning" question