在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。本文阿宝哥将跟大家一起学习一下 TS 类涉及的十个知识点。

一、类的属性与方法

1.1 类的成员属性和静态属性

在 TypeScript 中,我们可以通过 class 关键字来定义一个类:

class Person {name: string; // 成员属性constructor(name: string) { // 类的构造函数this.name = name;}
}

在以上代码中,我们使用 class 关键字定义了一个 Person 类,该类含有一个名为 name 的成员属性。其实 TypeScript 中的类是一个语法糖(所谓的语法糖就是在之前的某个语法的基础上改变了一种写法,实现的功能相同,但是写法不同了,主要是为了让开发人员在使用过程中更方便易懂。),若设置编译目标为 ES5 将会产生以下代码:

"use strict";
var Person = /** @class */ (function () {function Person(name) {this.name = name;}return Person;
}());

类除了可以定义成员属性外,还可以通过 static 关键字定义静态属性:

class Person {static cid: string = "exe";name: string; // 成员属性constructor(name: string) { // 类的构造函数this.name = name;}
}

那么成员属性与静态属性有什么区别呢?在回答这个问题之前,我们先来看一下编译生成的 ES5 代码:

"use strict";
var Person = /** @class */ (function () {function Person(name) {this.name = name;}Person.cid = "exe";return Person;
}());

观察以上代码可知,成员属性是定义在类的实例上,而静态属性是定义在构造函数上。

1.2 类的成员方法和静态方法

在 TS 类中,我们不仅可以定义成员属性和静态属性,还可以定义成员方法和静态方法,具体如下所示:

class Person {static cid: string = "exe";name: string; // 成员属性static printCid() { // 定义静态方法console.log(Person.cid);  }constructor(name: string) { // 类的构造函数this.name = name;}say(words: string) :void { // 定义成员方法console.log(`${this.name} says:${words}`);  }
}

那么成员方法与静态方法有什么区别呢?同样,在回答这个问题之前,我们先来看一下编译生成的 ES5 代码:

"use strict";
var Person = /** @class */ (function () {function Person(name) {this.name = name;}Person.printCid = function () {console.log(Person.cid);};Person.prototype.say = function (words) {console.log(this.name + " says\uFF1A" + words);};Person.cid = "exe";return Person;
}());

由以上代码可知,成员方法会被添加到构造函数的原型对象上,而静态方法会被添加到构造函数上。

1.3 类成员方法重载

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。 在定义类的成员方法时,我们也可以对成员方法进行重载:

class Person {constructor(public name: string) {}say(): void; say(words: string): void;say(words?: string) :void { // 方法重载if(typeof words === "string") {console.log(`${this.name} says:${words}`);  } else {console.log(`${this.name} says:Nothing`);  }}
}let p1 = new Person("Semlinker");
p1.say();
p1.say("Hello TS");

如果想进一步了解函数重载的话,可以继续阅读 是时候表演真正的技术了 - TS 分身之术

二、访问器

在 TypeScript 中,我们可以通过 gettersetter 方法来实现数据的封装和有效性校验,防止出现异常数据。

let passcode = "Hello TypeScript";class Employee {private _fullName: string = "";get fullName(): string {return this._fullName;}set fullName(newName: string) {if (passcode && passcode == "Hello TypeScript") {this._fullName = newName;} else {console.log("Error: Unauthorized update of employee!");}}
}let employee = new Employee();
employee.fullName = "Semlinker";

在以上代码中,对于私有的 _fullName 属性,我们通过对外提供 gettersetter 来控制该属性的访问和修改。

三、类的继承

继承(Inheritance)是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。通过类的继承,我们可以实现代码的复用。

继承是一种 is-a 关系:

在 TypeScript 中,我们可以通过 extends 关键字来实现类的继承:

3.1 父类

class Person {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }
}

3.2 子类

class Developer extends Person {constructor(name: string) {super(name);this.say("Learn TypeScript")}
}const p2 = new Developer("semlinker");
// 输出: "semlinker says:Learn TypeScript"

因为 Developer 类继承了 Person 类,所以我们可以在 Developer 类的构造函数中调用 say 方法。需要注意的是,在 TypeScript 中使用 extends 时,只能继承单个类:

class Programmer {}// Classes can only extend a single class.(1174)
class Developer extends Person, Programmer {constructor(name: string) {super(name);this.say("Learn TypeScript")}
}

虽然在 TypeScript 中只允许单继承,但却允许我们实现多个接口。具体的使用示例如下所示:

interface CanSay {say(words: string) :void
}interface CanWalk {walk(): void;
}class Person implements CanSay, CanWalk {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }public walk(): void {console.log(`${this.name} walk with feet`);}
}

此外,除了可以继承具体的实现类之外,在实现继承时,我们还可以继承抽象类。

四、抽象类

使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,因为它里面包含一个或多个抽象方法。 所谓的抽象方法,是指不包含具体实现的方法:

abstract class Person {constructor(public name: string){}abstract say(words: string) :void;
}// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error

抽象类不能被直接实例化,我们只能实例化实现了所有抽象方法的子类。具体如下所示:

class Developer extends Person {constructor(name: string) {super(name);}say(words: string): void {console.log(`${this.name} says ${words}`);}
}const lolo = new Developer("lolo");
lolo.say("I love ts!"); // 输出:lolo says I love ts!

五、类访问修饰符

在 TS 类型中,我们可以使用 publicprotectedprivate 来描述该类属性和方法的可见性。

5.1 public

public 修饰的属性或者方法是公有的,可以在任何地方被访问到,默认所有的属性或者方法都是 public:

class Person {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }
}

5.2 protected

protected 修饰的属性或者方法是受保护的,它和 private 类似,不同的地方是 protected 成员在派生类中仍然可以访问。

class Person {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }protected getClassName() {return "Person";}
}const p1 = new Person("lolo");
p1.say("Learn TypeScript"); // Ok
// Property 'getClassName' is protected and only accessible within class 'Person' and its subclasses.
p1.getClassName() // Error

由以上错误信息可知,使用 protected 修饰符修饰的方法,只能在当前类或它的子类中使用。

class Developer extends Person {constructor(name: string) {super(name);console.log(`Base Class:${this.getClassName()}`);}
}const p2 = new Developer("semlinker"); // 输出:"Base Class:Person"

5.3 private

private 修饰的属性或者方法是私有的,只能在类的内部进行访问。

class Person {constructor(private id: number, public name: string) {}
}const p1 = new Person(28, "lolo");
// Property 'id' is private and only accessible within class 'Person'.(2341)
p1.id // Error
p1.name // OK

由以上错误信息可知,使用 private 修饰符修饰的属性,只能在当前类内部访问。但真的是这样么?其实这只是 TS 类型检查器给我们的提示,在运行时我们还是可以访问 Person 实例的 id 属性。不相信的话,我们来看一下编译生成的 ES5 代码:

"use strict";
var Person = /** @class */ (function () {function Person(id, name) {this.id = id;this.name = name;}return Person;
}());
var p1 = new Person(28, "lolo");

5.4 私有字段

针对上面的问题,TypeScript 团队在 3.8 版本就开始支持 ECMAScript 私有字段,使用方式如下:

class Person {#name: string;constructor(name: string) {this.#name = name;}
}let semlinker = new Person("semlinker");
// Property '#name' is not accessible outside class 'Person' because it has a private identifier.
semlinker.#name // Error

那么 ECMAScript 私有字段private 修饰符相比,有什么特别之处么?这里我们来看一下编译生成的 ES2015 代码:

"use strict";
var __classPrivateFieldSet = // 省略相关代码
var _Person_name;
class Person {constructor(name) {_Person_name.set(this, void 0);__classPrivateFieldSet(this, _Person_name, name, "f");}
}_Person_name = new WeakMap();
let semlinker = new Person("Semlinker");

观察以上的结果可知,在处理私有字段时使用到了 ES2015 新增的 WeakMap 数据类型,如果你对 WeakMap 还不了解的话,可以阅读 你不知道的 WeakMapprivate 修饰符声明的属性)不同之处:

  • 私有字段以 # 字符开头,有时我们称之为私有名称;

  • 每个私有字段名称都唯一地限定于其包含的类;

  • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);

  • 私有字段不能在包含的类之外访问,甚至不能被检测到。

六、类表达式

TypeScript 1.6 添加了对 ES6 类表达式的支持。类表达式是用来定义类的一种语法。和函数表达式相同的一点是,类表达式可以是命名也可以是匿名的。如果是命名类表达式,这个名字只能在类体内部才能访问到。

类表达式的语法如下所示([] 方括号表示是可选的):

const MyClass = class [className] [extends] {// class body
};

基于类表达式的语法,我们可以定义一个 Point 类:

let Point = class {constructor(public x: number, public y: number) {}public length() {return Math.sqrt(this.x * this.x + this.y * this.y);}
}let p = new Point(3, 4);
console.log(p.length()); // 输出:5

需要注意在使用类表达式定义类的时候,我们也可以使用 extends 关键字。篇幅有限,这里就不展开介绍了,感兴趣的小伙伴可以自行测试一下。

七、泛型类

在类中使用泛型也很简单,我们只需要在类名后面,使用 <T, ...> 的语法定义任意多个类型变量,具体示例如下:

class Person<T> {constructor(public cid: T, public name: string) {}
}let p1 = new Person<number>(28, "Lolo");
let p2 = new Person<string>("exe", "Semlinker");

接下来我们以实例化 p1 为例,来分析一下其处理过程:

  • 在实例化 Person 对象时,我们传入 number 类型和相应的构造参数;

  • 之后在 Person 类中,类型变量 T 的值变成 number 类型;

  • 最后构造函数 cid 的参数类型也会变成 number 类型。

相信看到这里一些读者会有疑问,我们什么时候需要使用泛型呢?通常在决定是否使用泛型时,我们有以下两个参考标准:

  • 当你的函数、接口或类将处理多种数据类型时;

  • 当函数、接口或类在多个地方使用该数据类型时。

八、构造签名

在 TypeScript 接口中,你可以使用 new 关键字来描述一个构造函数:

interface Point {new (x: number, y: number): Point;
}

以上接口中的 new (x: number, y: number) 我们称之为构造签名,其语法如下:

ConstructSignature:newTypeParametersopt(ParameterListopt)TypeAnnotationopt

在上述的构造签名中,TypeParametersoptParameterListoptTypeAnnotationopt 分别表示:可选的类型参数、可选的参数列表和可选的类型注解。那么了解构造签名有什么用呢?这里我们先来看个例子:

interface Point {new (x: number, y: number): Point;x: number;y: number;
}class Point2D implements Point {readonly x: number;readonly y: number;constructor(x: number, y: number) {this.x = x;this.y = y;}
}const point: Point = new Point2D(1, 2); // Error

对于以上的代码,TypeScript 编译器(v4.4.3)会提示以下错误信息:

Type 'Point2D' is not assignable to type 'Point'.
Type 'Point2D' provides no match for the signature 'new (x: number, y: number): Point'.

要解决这个问题,我们就需要把对前面定义的 Point 接口进行分离:

interface Point {x: number;y: number;
}interface PointConstructor {new (x: number, y: number): Point;
}

完成接口拆分之后,除了前面已经定义的 Point2D 类之外,我们又定义了一个 newPoint 工厂函数,该函数用于根据传入的 PointConstructor 类型的构造函数,来创建对应的 Point 对象。

class Point2D implements Point {readonly x: number;readonly y: number;constructor(x: number, y: number) {this.x = x;this.y = y;}
}function newPoint( // 工厂方法pointConstructor: PointConstructor,x: number,y: number
): Point {return new pointConstructor(x, y);
}const point: Point = newPoint(Point2D, 1, 2);

九、抽象构造签名

在 TypeScript 4.2 版本中引入了抽象构造签名,用于解决以下的问题:

type ConstructorFunction = new (...args: any[]) => any;abstract class Utilities {}// Type 'typeof Utilities' is not assignable to type 'ConstructorFunction'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.
let UtilityClass: ConstructorFunction = Utilities; // Error.

由以上的错误信息可知,我们不能把抽象构造器类型分配给非抽象的构造器类型。针对这个问题,我们需要使用 abstract 修饰符:

declare type ConstructorFunction = abstract new (...args: any[]) => any;

需要注意的是,对于抽象构造器类型,我们也可以传入具体的实现类:

declare type ConstructorFunction = abstract new (...args: any[]) => any;abstract class Utilities {}
class UtilitiesConcrete extends Utilities {}let UtilityClass: ConstructorFunction = Utilities; // Ok
let UtilityClass1: ConstructorFunction = UtilitiesConcrete; // Ok

而对于 TypeScript 4.2 以下的版本,我们可以通过以下方式来解决上面的问题:

type Constructor<T> = Function & { prototype: T }abstract class Utilities {}class UtilitiesConcrete extends Utilities {}let UtilityClass: Constructor<Utilities> = Utilities;
let UtilityClass1: Constructor<UtilitiesConcrete> = UtilitiesConcrete;

介绍完抽象构造签名,最后我们来简单介绍一下 class type 与 typeof class type 的区别。

十、class type 与 typeof class type

class Person {static cid: string = "exe";name: string; // 成员属性static printCid() { // 定义静态方法console.log(Person.cid);  }constructor(name: string) { // 类的构造函数this.name = name;}say(words: string) :void { // 定义成员方法console.log(`${this.name} says:${words}`);  }
}// Property 'say' is missing in type 'typeof Person' but required in type 'Person'.
let p1: Person = Person; // Error
let p2: Person = new Person("Semlinker"); // Ok// Type 'Person' is missing the following properties from type 'typeof Person': prototype, cid, printCid
let p3: typeof Person = new Person("Lolo"); // Error
let p4: typeof Person = Person; // Ok

通过观察以上的代码,我们可以得出以下结论:

  • 当使用 Person 类作为类型时,可以约束变量的值必须为 Person 类的实例;

  • 当使用 typeof Person 作为类型时,可以约束变量的值必须包含该类上的静态属性和方法。

此外,需要注意的是 TypeScript 使用的是 结构化 类型系统,与 Java/C++ 所采用的 名义化 类型系统是不一样的,所以以下代码在 TS 中是可以正常运行的:

class Person {constructor(public name: string) {}
}class SuperMan {constructor(public name: string) {}
}let p1: SuperMan = new Person("Semlinker"); // Ok

好的,在日常工作中,TypeScript 类比较常见的知识,就介绍到这里,如果有遗漏的知识,欢迎小伙伴给阿宝哥留言哈。《重学 TS 2.0》 还在继续更新中,想系统学习 TS 的小伙伴,可以加阿宝哥微信,回复 TS 一起进群学习哈。

十一、参考资源

  • TypeScript 1.6

  • how-to-use-classes-in-typescript

【TS】1119- TS 类这十个知识点你都掌握了么?相关推荐

  1. 【TypeScript 专题】之 Ts 中的类(class)

    在 ES6 中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类.它本质仍然是函数,它让对象原型的写法更加清晰.更像面向对象编程的语法. 在 TypeScript 中,除了实 ...

  2. tcp通讯一次最多能发送多少数据?_关于TCP/IP,必须知道的十个知识点

    本文整理了一些TCP/IP协议簇中需要必知必会的十大问题,既是面试高频问题,又是程序员必备基础素养. 一.TCP/IP模型 TCP/IP协议模型(Transmission Control Protoc ...

  3. 网络协议:关于TCP/IP,必须知道的十个知识点

    关于TCP/IP,必须知道的十个知识点 本文整理了一些TCP/IP协议簇中需要必知必会的十大问题,既是面试高频问题,又是程序员必备基础素养. 一.TCP/IP模型 TCP/IP协议模型(Transmi ...

  4. Python语法之精妙的十个知识点(装B语法)

    文章目录 1. for - else 2. 一颗星(*)和两颗星(**) 3. 三元表达式 6. 列表索引的各种骚操作 7. lambda函数 8. yield 以及生成器和迭代器 9. 装饰器 10 ...

  5. 唐山初中计算机考试知识点,唐山:2017小升初英语必考的十个知识点(推荐)...

    原标题:唐山:2017小升初英语必考的十个知识点(推荐) 我是一名中考英语老师,主教中考英语,新概念英语,初中1-3:每天早上发一篇考试提分方法或教育心得,都是原创,不胡乱发!全是实战总结,不发虚头巴 ...

  6. TS学习(二) :安装ts与ts配置

    一.安装TypeScript npm i -g typescript 二.安装完成后 创建ts 使用ts语法 可能遇到的报错问题 在啥都没配置的默认情况下,TS会做出下面几种假设: 假设当前的执行环境 ...

  7. 【TS】ts的使用和类型注解

    全局安装Ts npm install -g typescript 检查ts版本 tsc -V 安装完ts后,在文件夹新建 xxx.ts文件,html可以引入,但此时引入ts会报错,解决问题如下: 在使 ...

  8. 编程:请写一个类,在任何时候都可以向它查询“你已经创建了多少个对象?”...

    Question:使用类的静态字段和构造函数,我们可以跟踪某个类所创建对象的个数.请写一个类,在任何时候都可以向它查询"你已经创建了多少个对象?". Answer: public ...

  9. 国内的IT类的技术博客都有哪些?

    国内的IT类的技术博客都有哪些? 博客园.csdn.51cto技术博客

最新文章

  1. SQL Server 高可用性(三)共享磁盘
  2. java测试时找不到类,我在class文件运行不了测试文件,提示找junit4找不到任何的测试项目...
  3. Linux程序开机启动
  4. JavaScript——String转DOM对象解决方案
  5. java模拟atm 课程设计_急求,关于Java课程设计ATM创建实现
  6. 并不是所有SAP产品的UX,都得遵循Fiori UX风格
  7. 11种方法激励科技人才
  8. python123第五周作业答案_马哥2016全新Linux+Python高端运维班第五周作业
  9. 开源免费的.NET图像即时处理的组件ImageProcessor
  10. php微信支付接口开发程序(概念篇)
  11. apple开发过程中错误汇总ios+macos
  12. api 原生hbase_数据查询的玄铁剑:云HBase原生二级索引发布
  13. C#扩展名关联【转】
  14. 用html+css制作简单好看的登陆注册界面
  15. Windows环境下sublime text 3搭建前端开发环境
  16. html 半个字符,半角字符什么意思
  17. 我的NVIDIA开发者之旅——优化显卡性能
  18. 软件开发自学靠谱吗?
  19. hadoop组件---面向列的开源数据库(九)--python--python使用thrift连接hbase
  20. Android Studio如何更改app名称

热门文章

  1. 4G网络数据传输流程 三
  2. 互联网晚报 | 11月23日 星期二 | 格力电器宣布实行双休工作制;知乎月活跃用户首次突破1亿大关;微博入局英雄联盟...
  3. Vuforia SDK---- AR开发vuforia 问题总结
  4. 绿色版 MySQL 安装配置的正确操作步骤
  5. Spring Boot内置Tomcat的静态资源配置(在页面中显示项目外的某个图片)
  6. nginx配置本地静态资源路径
  7. 东北电力计算机考研分数线,东北电力大学考研分数线_2021考研国家分数线什么时候出来...
  8. 新大西洋海底光缆:传输速度达160Tbps
  9. 拨乱反正:“通过RAMDirectory缓写提高性能”是错误的!
  10. C++控制台操作(基本操作的代码)