TypeScript学习:TypeScript从入门到精通
TypeScript类(上篇):TypeScript类(上篇)
蓝桥杯真题解析:蓝桥杯Web国赛真题解析
个人简介:即将大三的学生,热爱前端,热爱生活
你的一键三连是我更新的最大动力❤️!
最近博主一直在创作
TypeScript
的内容,所有的TypeScript
文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅❤️
本篇文章将继续去讲解TypeScript
中的class
类(由于内容较多,将其分为了上下两篇,查看上篇内容请点击这里),这也许会是你看过的最全面最细致的TypeScript
教程,点赞关注收藏不迷路!
类和接口一样,可以是泛型的,当一个泛型类用new
实例化时,其类型参数的推断方式与函数调用的方式相同:
class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}
// const b: Box
const b = new Box("hello!");
// 等同于const b = new Box("hello!");
在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
在【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
调用。
在类中,一个叫做 this
的特殊类型动态地指向当前类的类型,看下面的这个例子:
class Box {
contents: string = "";
set(value: string) {
this.contents = value;
return this;
}
}
在这里,TypeScript
推断出 set
方法的返回类型是this
,而不是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));
我们可以在类和接口的方法的返回位置使用类型谓词this is Type
,当与类型缩小混合时(例如if
语句),目标对象的的类型将被缩小到指定的Type
类型谓词详见【TypeScript】TypeScript中类型缩小(含类型保护)与类型谓词
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());
}
类表达式与类声明非常相似,唯一真正的区别是,类表达式不需要一个名字,我们可以通过它们最终绑定的任何标识符来引用它们:
const someClass = class<Type> {
content: Type;
constructor(value: Type) {
this.content = value;
}
};
// type m=someClass
const m = new someClass("Hello, world");
使用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);
相同的类可以互相替代使用:
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官网
如果本篇文章对你有所帮助,还请客官一件四连!❤️